All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.skuzzle.test.snapshots.data.xml.XmlSnapshot Maven / Gradle / Ivy

package de.skuzzle.test.snapshots.data.xml;

import java.util.Map;
import java.util.function.Consumer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import de.skuzzle.test.snapshots.ComparisonRuleBuilder;
import de.skuzzle.test.snapshots.SnapshotSerializer;
import de.skuzzle.test.snapshots.StructuralAssertions;
import de.skuzzle.test.snapshots.StructuredData;
import de.skuzzle.test.snapshots.StructuredDataProvider;
import de.skuzzle.test.snapshots.data.xml.xmlunit.XPathDebug;
import de.skuzzle.test.snapshots.data.xml.xmlunit.XmlUnitStructuralAssertions;
import de.skuzzle.test.snapshots.reflection.Classes;
import de.skuzzle.test.snapshots.reflection.StackTraces;
import de.skuzzle.test.snapshots.validation.Arguments;
import de.skuzzle.test.snapshots.validation.State;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.xmlunit.assertj.CompareAssert;
import org.xmlunit.diff.DifferenceEvaluator;

/**
 * {@link StructuredData} builder for serializing test results to XML, relying on JAXB and
 * XML-Unit.
 * 

* You can either use a pre-configured default instance via {@link #xml} or use any of the * static factory methods to customize the construction. * * @author Simon Taddiken */ @API(status = Status.STABLE) public final class XmlSnapshot implements StructuredDataProvider { @Deprecated(forRemoval = true) static final boolean LEGACY_WARNING_PRINTED; static { final boolean placeHolderAvailable = Classes.isClassPresent("de.skuzzle.test.snapshots.data.xmlx.PlaceHolder"); if (!placeHolderAvailable) { System.err.println( "DEPRECATION WARNING: Starting from snapshot-tests version 1.10.0, you should depend on 'snapshot-tests-xml-legacy' module."); System.err.println(); System.err.println("To remove this warning, follow these migration steps:"); System.err.println(); System.err.println("- Remove direct dependency to 'snapshot-tests-jaxb'"); System.err.println("- Add direct dependency to 'snapshot-tests-xml-legacy' instead"); LEGACY_WARNING_PRINTED = true; } else { LEGACY_WARNING_PRINTED = false; } } /** * Simple default {@link StructuredData} instance which infers the JAXB context from a * test's actual result object. *

* If you need control over how the {@link JAXBContext} and the {@link Marshaller} are * being set up, use the static factory methods in {@link XmlSnapshot} instead of this * static constant. * * @see #xml() */ public static final StructuredDataProvider xml = xml().build(); // If left null, the JAXBContext will be inferred from the actual test result. private JAXBContext jaxbContext; // Creates the Marshaller from the JAXBContext private MarshallerSupplier marshallerSupplier; // Defines how snapshots are being asserted on using xml-unit private Consumer compareAssertConsumer = CompareAssert::areIdentical; // null unless customized private Consumer rules = null; // used only when actual test result is already a string private boolean prettyPrintStringXml = true; // Allows to print debug information for custom rule xpaths private XPathDebug xPathDebug = XPathDebug.disabled(); // namespaces to be used in XPath expressions when using custom rules private Map namespaceContext = null; private XmlSnapshot() { this.marshallerSupplier = ctx -> { final Marshaller marshaller = ctx.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); return marshaller; }; } /** * Tries to infer the JAXBContext from the passed in actual test result. * * @return A builder for building {@link StructuredData}. * @deprecated Since 1.4.0 - Use {@link #xml()} instead. */ @Deprecated(since = "1.4.0", forRemoval = true) @API(status = Status.DEPRECATED, since = "1.4.0") public static XmlSnapshot inferJaxbContext() { return xml(); } /** * Creates a new XML {@link StructuredDataProvider} which will try to infer the * {@link JAXBContext} from the actual test result. * * @return A builder for building {@link StructuredData}. * @since 1.4.0 */ @API(status = Status.STABLE, since = "1.4.0") public static XmlSnapshot xml() { return new XmlSnapshot(); } /** * Uses the given JAXBContext as entry point for serializing snapshots. * * @param jaxbContext The JAXBContext to use. * @return A builder for building {@link StructuredData}. * @deprecated Since 1.4.0 - Use {@link #withJAXBContext(JAXBContext)} instead. */ @Deprecated(since = "1.4.0", forRemoval = true) @API(status = Status.DEPRECATED, since = "1.4.0") public static XmlSnapshot with(JAXBContext jaxbContext) { return xml().withJAXBContext(jaxbContext); } /** * Uses the given {@link JAXBContext} instead of trying to infer it from the test * result. * * @param jaxbContext The JAXBContext to use. * @return This builder instance. */ public XmlSnapshot withJAXBContext(JAXBContext jaxbContext) { this.jaxbContext = Arguments.requireNonNull(jaxbContext, "jaxbContext must not be null"); return this; } /** * Supplies the {@link Marshaller} which will be used to serialize the snapshot to * xml. * * @param marshallerSupplier The supplier. * @return This builder instance. */ public XmlSnapshot withMarshaller(MarshallerSupplier marshallerSupplier) { this.marshallerSupplier = Arguments.requireNonNull(marshallerSupplier, "marshallerSupplier must not be null"); return this; } /** * Only taken into account if you directly pass a String into the snapshot test which * is already a XML is does not need to be serialized. In this case, you can advise * the framework to pretty print the passed in string before persisting it as a * snapshot. *

* For non-xml input (java classes that need to be serialized), pretty printing can be * controlled via cusomization of the marshaller using * {@link #withMarshaller(MarshallerSupplier)}. *

* Defaults to true. * * @param prettyPrintStringXml Whether to pretty print XML strings. * @return This build instance. * @since 1.6.0 */ @API(status = Status.EXPERIMENTAL, since = "1.6.0") public XmlSnapshot withPrettyPrintStringXml(boolean prettyPrintStringXml) { this.prettyPrintStringXml = prettyPrintStringXml; return this; } /** * Defines which Xml-Assert assertion method will actually be used. Defaults to * {@link CompareAssert#areIdentical()}. *

* You can also use this to apply further customizations to the CompareAssert. Consult * the xml-unit documentation for further information. *

* Note: if you also use {@link #withComparisonRules(Consumer)}, you can not * use {@link CompareAssert#withDifferenceEvaluator(DifferenceEvaluator)} here, as * your {@linkplain DifferenceEvaluator} will always be overridden by the one that is * configured in {@linkplain #withComparisonRules(Consumer)}. * * @param xmls Consumes the {@link CompareAssert} which compares the actual and * expected xml. * @return This builder instance. */ @API(status = Status.EXPERIMENTAL) public XmlSnapshot compareUsing(Consumer xmls) { this.compareAssertConsumer = Arguments.requireNonNull(xmls, "CompareAssert consumer must not be null"); return this; } /** * Enables a simple debug output to System.out for the xpaths that are used in * {@link #withComparisonRules(Consumer)}. This will print out all the nodes that are * matched by the xpaths that are used in custom comparison rules. *

* Note that this method must be called before calling * {@link #withComparisonRules(Consumer)}. * * @param enableXPathDebugging Whether to enable debug output for xpaths used in * {@link #withComparisonRules(Consumer)}. * @return This instance. * @since 1.6.0 */ @API(status = Status.EXPERIMENTAL, since = "1.6.0") public XmlSnapshot withEnableXPathDebugging(boolean enableXPathDebugging) { State.check(this.rules == null, "xpath debugging must be enabled before specifying custom comparison rules"); this.xPathDebug = enableXPathDebugging ? XPathDebug.enabledAt(StackTraces.findImmediateCaller()) : XPathDebug.disabled(); return this; } /** * Allows to specify extra comparison rules that are applied to certain paths within * the xml snapshots. *

* Paths on the {@link ComparisonRuleBuilder} must conform to standard XPath syntax. * You can enable debug output for xpath expressions using * {@link #withEnableXPathDebugging(boolean)}. Note that debug output must be enabled * before calling this method. *

* If you intend to use custom rules for XMLs containing namespaces, you need to * configure a namespace context via {@link #withXPathNamespaceContext(Map)}. * *

     * XmlSnapshot.xml()
     *         .withNamespaceContext(Map.of("ns1", "foo:1", "ns2", "foo:2"))
     *         .withComparisonRules(rules -> rules
     *                 .pathAt("/ns1:root/ns2:child/text()").ignore())
     * 
* * The above XPath custom rule would match "some text" in the given XML: * *
     * <whatever:root xmlns:whatever="foo:1" xmlns:doesntmatter="foo:2">
     *     <doesntmatter:child>some text</doesntmatter:child>
     * </whatever:root>
     * 
* * As demonstrated here, you only need to make sure that you are providing the correct * namespace URIs. The prefix names that you use in the XPath do not need to match the * prefix names within the matched documents. * *

* Note: This will customize the {@link DifferenceEvaluator} that is used. Thus you * can not use this method in combination with {@link #compareUsing(Consumer)} if you * intend to use an own {@link DifferenceEvaluator}. * * @param rules A consumer to which a {@link ComparisonRuleBuilder} will be passed. * @return This instance. * @since 1.3.0 */ @API(status = Status.EXPERIMENTAL, since = "1.3.0") public XmlSnapshot withComparisonRules(Consumer rules) { Arguments.requireNonNull(rules, "rules consumer must not be null"); this.rules = rules; return this; } /** * Allows to set a namespace context by providing a mapping from prefixes to xml * namespace URIs. *

* If you intend to use {@link #withComparisonRules(Consumer) custom comparison rules} * on XMLs with namespaces it is mandatory to define a namespace context. XPath * comparison does not automatically fall back to just using local names. *

* The prefixes that you can configure here are only relevant to the XPath expression * itself and do not need to match the prefixes of the compared XMLs. However, the * URIs must be identical. *

* Note: You must set the namespace context before configuring the custom rules. * * @param namespaceContext A mapping of prefixes to xml namespace URIs. * @return This instance. * @see #withComparisonRules(Consumer) * @since 1.9.0 */ @API(status = Status.EXPERIMENTAL, since = "1.9.0") public XmlSnapshot withXPathNamespaceContext(Map namespaceContext) { State.check(this.rules == null, "namespaceContext must be enabled before specifying custom comparison rules"); this.namespaceContext = Arguments.requireNonNull(namespaceContext, "namespaceContext must not be null"); return this; } @Override public StructuredData build() { final SnapshotSerializer snapshotSerializer = JaxbXmlSnapshotSerializer.withExplicitJaxbContext( jaxbContext, marshallerSupplier, prettyPrintStringXml); final StructuralAssertions structuralAssertions = new XmlUnitStructuralAssertions( compareAssertConsumer, rules, namespaceContext, xPathDebug); return StructuredData.with(snapshotSerializer, structuralAssertions); } @FunctionalInterface public interface MarshallerSupplier { Marshaller createMarshaller(JAXBContext jaxbContext) throws JAXBException; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy