Как написать Unit Test для JAXB 2.0 Marshalling

Я использую Jaxb 2.0 api без использования XSD и создал модель контента с помощью аннотаций. Я хочу написать тест Junit для класса, который выполняет сортировку. Мой первоначальный план состоял в том, чтобы сравнить ожидаемую строку XML с фактической для утверждения (наиболее очевидный выбор). Но я нахожу, что marshalling создает xml, где порядок свойств/атрибутов не предсказуем (на самом деле я не знаю, что является порядком по умолчанию). Теперь, если это так, я не могу принять предопределенную строку xml, а затем сравнить ее с маршаллированной. Другой способ, который я думал о утверждении класса маршаллера, был следующим:

1-Создать модель содержимого.

2-Маршалл это.

3-Unmarshall xml, созданный на шаге 2, чтобы получить модель.

Утверждение 4-Do на основе модели на шагах 1 и 3 для свойств/атрибутов.

Но я до сих пор не считаю это удовлетворительным. Каким будет правильный способ написать тест Junit для сортировки в этом сценарии?

Хотя фактическое приложение, которое использует marshalled xml, не зависит от порядка свойств/атрибутов xml, но тест Junit кажется сложным.

Спасибо

Ответ 1

Я наткнулся на ваш вопрос, когда искал то же самое. Если вы нашли это сообщение , но ему не понравилась идея "разобрать" сгенерированный XML впоследствии. После просеивания через JAXB Javadoc я нашел подход, который мне очень нравится. JAXB Marshaller предлагает метод, который принимает SAX ContentHandler в качестве аргумента. Вы можете издеваться над этим ContentHandler и убедиться, что определенные методы были вызваны с ожидаемыми аргументами.

Вот небольшой пример. Я написал собственный Attributes -сервер, который проверяет наличие только определенных локальных имен атрибутов, но не смотрит на значения (пока). Надеюсь, вы сочтете это полезным:

@Mock
private ContentHandler handler;

private JAXBContext context;
private ObjectFactory factory;
private Marshaller marshaller;

@Before
public void setUp() throws Exception
{
    context = JAXBContext.newInstance(getClass().getPackage().getName());
    factory = new ObjectFactory();
    marshaller = context.createMarshaller();
}

@Test
public void test() throws Exception
{
    final UpdateDescription description = new UpdateDescription("identifier", "version");
    final JAXBElement<UpdateDescription> element = factory.createUpdateDescription(description);

    marshaller.marshal(element, handler);

    verify(handler).startDocument();
    verify(handler).startElement(anyString(), eq("description"), anyString(), any(Attributes.class));
    verify(handler).startElement(anyString(), eq("identifier"), anyString(), attrs("value"));
    verify(handler).startElement(anyString(), eq("version"), anyString(), attrs("value"));
    verify(handler).endDocument();
}

private static Attributes attrs(final String... localNames)
{
    final Matcher<Attributes> matcher = new TypeSafeMatcher<Attributes>()
    {
        private Set<String> names = Sets.<String> newHashSet(localNames);

        @Override
        public void describeTo(final Description description)
        {
            // TODO Auto-generated method stub
        }

        @Override
        public boolean matchesSafely(final Attributes item)
        {
            final Set<String> presentLocalNames = Sets.newHashSetWithExpectedSize(item.getLength());
            final int length = item.getLength();
            for (int i = 0; i < length; ++i) {
                presentLocalNames.add(item.getLocalName(i));
            }

            return Sets.difference(names, presentLocalNames).isEmpty();
        }
    };
    return new ThreadSafeMockingProgress().getArgumentMatcherStorage().reportMatcher(matcher).returnFor(
            new AttributesImpl());
}

Ответ 2

Для тех, кто предпочитает более простой тест, вот что я собрал из поста, связанного с ответом RobertB, и ответы здесь:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;

public class JaxbTestHelper {

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object jaxbMarshalUnmarshal(Object schemaObject) throws Exception {
        JAXBContext context = JAXBContext.newInstance(schemaObject.getClass());
        Marshaller marshaller = context.createMarshaller();
        Unmarshaller unmarshaller = context.createUnmarshaller();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        Object unmarshalledObject = null;

        try {
            marshaller.marshal(schemaObject, output);
            ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
            unmarshalledObject = unmarshaller.unmarshal(input);
        } catch (JAXBException e) {
            // object class not annotated with @XmlRootElement, so we have to "wrap" and "unwrap" the object
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.marshal(new JAXBElement(new QName("uri", "local"), schemaObject.getClass(), schemaObject),
                    output);

            StreamSource source = new StreamSource(new ByteArrayInputStream(output.toByteArray()));
            unmarshalledObject = unmarshaller.unmarshal(source, schemaObject.getClass()).getValue();
        }

        // callers should verify this returned object equals the schema object passed in
        // ie, mySchemaObject.equals(jaxbMarshalUnmarshal(mySchemaObject))
        return unmarshalledObject;
    }

}

Ответ 3

Я столкнулся с той же проблемой тестирования XML-маршаллинга. Вы можете использовать XmlUnit библиотеку для сравнения сериализованного xml с эталонным. XmlUnit может сравнивать два xml и поддерживает такие функции, как игнорирование пространства, переупорядочение элементов и некоторые другие.

Вот хорошая статья от IBM developerWorks о XmlUnit,
хотя описывает более старую версию XmlUnit, она дает хорошее объяснение и примеры.

Сравнение xml может выглядеть так:

Diff diff = DiffBuilder
            .compare(expectXml)
            .withTest(marshaledXml)
            //Ignore element order
            .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
            .ignoreWhitespace()
            .ignoreComments()
            .checkForSimilar()
            .build()

    assert !diff.hasDifferences()

Ответ 4

на самом деле вы можете написать ожидаемый результат для сравнения с тем, что генерируется jaxb, не забудьте добавить "\n" в конце ожидаемого результата, который может вызвать ошибку утверждения