{"id":15714575,"url":"https://github.com/origin-energy/java-snapshot-testing","last_synced_at":"2025-04-13T06:40:29.786Z","repository":{"id":38361387,"uuid":"203931732","full_name":"origin-energy/java-snapshot-testing","owner":"origin-energy","description":"Facebook style snapshot testing for JAVA Tests","archived":false,"fork":false,"pushed_at":"2024-04-22T11:15:53.000Z","size":711,"stargazers_count":104,"open_issues_count":9,"forks_count":14,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-04-22T12:32:34.469Z","etag":null,"topics":["java","jest","snapshot"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/origin-energy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-23T05:44:54.000Z","updated_at":"2024-05-18T10:23:54.262Z","dependencies_parsed_at":"2023-12-14T22:54:35.051Z","dependency_job_id":"a7be638a-081d-4aa1-adb8-5f63a6cedcc8","html_url":"https://github.com/origin-energy/java-snapshot-testing","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/origin-energy%2Fjava-snapshot-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/origin-energy%2Fjava-snapshot-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/origin-energy%2Fjava-snapshot-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/origin-energy%2Fjava-snapshot-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/origin-energy","download_url":"https://codeload.github.com/origin-energy/java-snapshot-testing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675434,"owners_count":21143763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","jest","snapshot"],"created_at":"2024-10-03T21:38:31.782Z","updated_at":"2025-04-13T06:40:29.761Z","avatar_url":"https://github.com/origin-energy.png","language":"Java","readme":"[![Build Status](https://github.com/origin-energy/java-snapshot-testing/workflows/build/badge.svg)](https://github.com/origin-energy/java-snapshot-testing/actions)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.origin-energy/java-snapshot-testing-core/badge.svg)](https://search.maven.org/artifact/io.github.origin-energy/java-snapshot-testing-core/3.2.7/jar)\n\n# Java Snapshot Testing\n- Inspired by [facebook's Jest framework](https://facebook.github.io/jest/docs/en/snapshot-testing.html)\n\n🎉 4.0.0 is out\n\n## Upgrading\n- Upgrade guide from 3.X to 4.X [here](https://github.com/origin-energy/java-snapshot-testing/discussions/94)\n- Upgrade guide from 2.X to 3.X [here](https://github.com/origin-energy/java-snapshot-testing/discussions/73)\n- Upgrade guide from 2.X-BETA to 2.X [here](https://github.com/origin-energy/java-snapshot-testing/discussions/58)\n\n## The testing framework loved by ~~lazy~~ __productive__ devs\n\n- Tired of needing to `assertThat(foo).isEqualTo(\"bar\")` again \u0026 again?\n- Are you just wanting to ensure you don't break - for example - REST interfaces\n- Are you manually saving text files for verification in your tests?\n\n**Want a better way?**\nThen java-snapshot-testing might just be what you are looking for!\n\n## Quick Start (Junit5 + Gradle example)\n\n1. Add test dependencies\n\n```groovy\n// In this case we are using the JUnit5 testing framework\ntestImplementation 'io.github.origin-energy:java-snapshot-testing-junit5:4.+'\n\n// slf4j logging implementation if you don't already have one\ntestImplementation(\"org.slf4j:slf4j-simple:2.0.0-alpha0\")\n\n// Optional: Many will want to serialize into JSON.  In this case you should also add the Jackson plugin\ntestImplementation 'io.github.origin-energy:java-snapshot-testing-plugin-jackson:4.+'\ntestImplementation 'com.fasterxml.jackson.core:jackson-core:2.11.3'\ntestImplementation 'com.fasterxml.jackson.core:jackson-databind:2.11.3'\n\n// Optional: If you want Jackson to serialize Java 8 date/time types or Optionals you should also add the following dependencies\ntestRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.3'\ntestRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.3'\n```\n\n2. Create `snapshot.properties` and configure your global settings. Be sure to set `output-dir` appropriately for your\n   JVM language.\n\n- /src/test/resources/snapshot.properties\n\n ```text\nserializer=au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer\nserializer.base64=au.com.origin.snapshots.serializers.v1.Base64SnapshotSerializer\nserializer.json=au.com.origin.snapshots.jackson.serializers.v1.JacksonSnapshotSerializer\nserializer.orderedJson=au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer\ncomparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator\nreporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter\nsnapshot-dir=__snapshots__\noutput-dir=src/test/java\nci-env-var=CI\nupdate-snapshot=none\n```\n\n3. Enable snapshot testing and write your first test\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Expect;\nimport au.com.origin.snapshots.annotations.SnapshotName;\nimport au.com.origin.snapshots.junit5.SnapshotExtension;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@ExtendWith({SnapshotExtension.class})\npublic class MyFirstSnapshotTest {\n\n    private Expect expect;\n\n    @SnapshotName(\"i_can_give_custom_names_to_my_snapshots\")\n    @Test\n    public void toStringSerializationTest() {\n        expect.toMatchSnapshot(\"Hello World\");\n    }\n\n    @Test\n    public void jsonSerializationTest() {\n        Map\u003cString, Object\u003e map = new HashMap\u003c\u003e();\n        map.put(\"name\", \"John Doe\");\n        map.put(\"age\", 40);\n\n        expect\n                .serializer(\"json\")\n                .toMatchSnapshot(map);\n    }\n\n}\n```\n\n4. Run your test\n\nBingo - you should now see your snapshot in the `__snapshots__` folder created next to your test. Try\nchanging `\"Hello World\"` to `\"Hello Universe\"` and watch it fail with a `.debug` file.\n\n```text\nau.com.origin.snapshots.docs.MyFirstSnapshotTest.jsonSerializationTest=[\n  {\n    \"age\": 40,\n    \"name\": \"John Doe\"\n  }\n]\n\n\ni_can_give_custom_names_to_my_snapshots=[\nHello World\n]\n```\n\n## Advantages of Snapshot Testing\n\n- Great for testing JSON interfaces ensuring you don't break clients\n- Fast and easy to test\n- Will implicitly test areas of your code you did not think about\n- Great of testing dynamic objects\n\nYou're responsible for making sure your generated snapshots do not include platform specific or other non-deterministic\ndata.\n\n## Disadvantages of Snapshot Testing\n\n- You need to ensure your test is deterministic for all fields (there are ways to ignore things like dates)\n- Does not give great insight to why the snapshot failed\n- Can be difficult to troll though large snapshot changes where you might only be interested in a small set of fields\n\n## Installation [Maven](https://search.maven.org/search?q=java-snapshot-testing)\n\nThese docs are for the latest `-SNAPSHOT` version published to maven central. Select the tag `X.X.X` matching your maven\ndependency to get correct documentation for your version.\n\nOnly if you want to integrate with an unsupported framework. [Show me how!](#using-an-unsupported-framework)\n\n- [Core](https://search.maven.org/search?q=a:java-snapshot-testing-core)\n\nWe currently support:\n\n- [JUnit4](https://search.maven.org/search?q=a:java-snapshot-testing-junit4)\n- [JUnit5](https://search.maven.org/search?q=a:java-snapshot-testing-junit5)\n- [Spock](https://search.maven.org/search?q=a:java-snapshot-testing-spock)\n\nPlugins\n\n- [Jackson for JSON serialization](https://search.maven.org/search?q=a:java-snapshot-testing-plugin-jackson)\n    - You need jackson on your classpath (Gradle example)\n      ```groovy\n         // Required java-snapshot-testing peer dependencies\n         testImplementation 'com.fasterxml.jackson.core:jackson-core:2.11.3'\n         testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.11.3'\n         // Optional java-snapshot-testing peer dependencies\n         testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.3'\n         testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.3'\n      ```\n\n## How does it work?\n\n1. When a test runs for the first time, a `.snap` file is created in a `__snapshots__` sub-directory\n1. On subsequent test runs, the `.snap` file is compared with the one produced by the test\n1. If they don't match, the test fails and a `.snap.debug` with the conflict is created\n1. It is then your job to decide if you have introduced a regression or intentionally changed the output (Use your IDE\n   file comparison tools to compare the two files or refer to the terminal output)\n1. If you have introduced a regression you will need to fix your code\n1. If you have intentionally changed the output you can manually modify the `.snap` file to make it pass or delete it\n   and it will be generated again from scratch\n1. Once you fix the test, the `*.snap.debug` file will get deleted\n\n## What is a Snapshot?\n\nA text representation of your java object (toString() or JSON).\n\n**String snapshot example**\n\n```java\nexpect.toMatchSnapshot(\"Hello World\");\n```\n\n```text\nau.com.example.company.HelloWorldTest.helloWorld=[\nHello world\n]\n```\n\n**JSON Snapshot Example**\n\n```java\nexpect.serializer(\"json\").toMatchSnapshot(userDto);\n```\n\n```text\nau.com.example.company.UserEndpointTest.shouldReturnCustomerData=[\n  {\n    \"id\": \"1\",\n    \"firstName\": \"John\",\n    \"lastName\": \"Smith\",\n    \"age\": 34\n  }\n]\n```\n\n# Usage Examples\n\nAll frameworks allow injection of the `Expect expect` via instance variable or method argument. In cases where\nparameterised tests are used, it's often better to use an instance variable in order to avoid conflicts with\nthe underlying data table. \n\nNote: Due to the above restriction, method argument injection is destined for removal in future versions.\n\n## [JUnit 5](https://junit.org/junit5)\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.junit5.SnapshotExtension;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport au.com.origin.snapshots.Expect;\n\n// Ensure you extend your test class with the SnapshotExtension\n@ExtendWith({SnapshotExtension.class})\npublic class JUnit5Example {\n\n  // Option 1: inject Expect as an instance variable\n  private Expect expect;\n\n  @Test\n  public void myTest1() {\n    // Verify your snapshot\n    expect.toMatchSnapshot(\"Hello World\");\n  }\n\n  // Option 2: inject Expect into the method signature\n  @Test\n  public void myTest2(Expect expect) {\n    expect.toMatchSnapshot(\"Hello World Again\");\n  }\n}\n```\n\n## [JUnit 4](https://junit.org/junit4)\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.annotations.SnapshotName;\nimport au.com.origin.snapshots.junit4.SnapshotRunner;\nimport au.com.origin.snapshots.Expect;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n// Ensure you RunWith the SnapshotRunner\n@RunWith(SnapshotRunner.class)\npublic class JUnit4Example {\n\n  // Option 1: inject Expect as an instance variable\n  private Expect expect;\n\n  @SnapshotName(\"my first test\")\n  @Test\n  public void myTest1() {\n    // Verify your snapshot\n    expect.toMatchSnapshot(\"Hello World\");\n  }\n\n  @SnapshotName(\"my second test\")\n  @Test\n  // Option 2: inject Expect into the method signature\n  public void myTest2(Expect expect) {\n    expect.toMatchSnapshot(\"Hello World Again\");\n  }\n}\n```\n\nIn order to run alongside another JUnit4 test runner such as `@RunWith(Parameterized.class)`, you need to use the \nRule based configuration instead.\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Expect;\nimport au.com.origin.snapshots.annotations.SnapshotName;\nimport au.com.origin.snapshots.junit4.SnapshotClassRule;\nimport au.com.origin.snapshots.junit4.SnapshotRule;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\n\npublic class JUnit4RulesExample {\n\n    @ClassRule\n    public static SnapshotClassRule snapshotClassRule = new SnapshotClassRule();\n\n    @Rule\n    public SnapshotRule snapshotRule = new SnapshotRule(snapshotClassRule);\n\n    private Expect expect;\n\n    @SnapshotName(\"my first test\")\n    @Test\n    public void myTest1() {\n        expect.toMatchSnapshot(\"Hello World\");\n    }\n}\n```\n\nSee the [ParameterizedTest](https://github.com/origin-energy/java-snapshot-testing/blob/master/java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/ParameterizedTest.java) for an example implementation\n\n## [Spock](http://spockframework.org/)\n\n```groovy\npackage au.com.origin.snapshots.docs\n\nimport au.com.origin.snapshots.annotations.SnapshotName\nimport au.com.origin.snapshots.spock.EnableSnapshots\nimport spock.lang.Specification\n\nimport au.com.origin.snapshots.Expect\n\n// Ensure you enable snapshot testing support\n@EnableSnapshots\nclass SpockExample extends Specification {\n\n    // Option 1: inject Expect as an instance variable\n    private Expect expect\n\n    // With spock tests you should always use @SnapshotName - otherwise they become coupled to test order\n    @SnapshotName(\"should_use_extension\")\n    def \"Should use extension\"() {\n        when:\n        expect.toMatchSnapshot(\"Hello World\")\n\n        then:\n        true\n    }\n\n    @SnapshotName(\"should_use_extension_as_method_argument\")\n    // Option 2: inject Expect into the method signature\n    def \"Should use extension as method argument\"(Expect expect) {\n        when:\n        expect.toMatchSnapshot(\"Hello World\")\n\n        then:\n        true\n    }\n}\n```\n\n# Using an unsupported framework\n\nThis library is in no way restricted to JUnit4, Junit5 or Spock.\n\nAny framework can support the library as long as it follows the following rules:\n\n1. Before all the tests in a single file execute (once only)\n    ```java\n        SnapshotVerifier snapshotVerifier = new SnapshotVerifier(new YourFrameworkSnapshotConfig(), testClass, failOnOrphans);\n    ```\n1. After all the tests in a single file execute (once only)\n    ```java\n      snapshotVerifier.validateSnapshots();\n    ```\n1. For each test class, setup your expectations\n    ```java\n       Expect expect = Expect.of(snapshotVerifier, testMethod);\n       expect.toMatchSnapshot(\"Something\");\n    ```\n   \nHere is a JUnit5 example that does not use the JUnit5 extension\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Expect;\nimport au.com.origin.snapshots.SnapshotVerifier;\nimport au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\n// Notice we aren't using any framework extensions\npublic class CustomFrameworkExample {\n\n    private static SnapshotVerifier snapshotVerifier;\n\n    @BeforeAll\n    static void beforeAll() {\n        snapshotVerifier = new SnapshotVerifier(new PropertyResolvingSnapshotConfig(), CustomFrameworkExample.class);\n    }\n\n    @AfterAll\n    static void afterAll() {\n        snapshotVerifier.validateSnapshots();\n    }\n\n    @Test\n    void shouldMatchSnapshotOne(TestInfo testInfo) {\n        Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get());\n        expect.toMatchSnapshot(\"Hello World\");\n    }\n\n}\n```\n\n## Supplying your own snapshot name via @SnapshotName\nBy default, snapshots use the full method name as the identifier \nFor example\n```text\nau.com.origin.snapshots.docs.MyFirstSnapshotTest.helloWorldTest=[\nHello World\n]\n```\n\nThis strategy has a number of problems\n- it's long and unwieldy\n- if the method name or class name changes, your tests become orphans\n- The Spock framework tests use a generated method name that is based on index (Spock tests should always use `@SnapshotName`)\n\nYou can supply a more meaningful name to your snapshot using `@SnapshotName(\"your_custom_name\")`\nThis will generate as follows\n```\nyour_custom_name=[\nHello World\n]\n```\n\nMuch more concise and not affected by class name or method name refactoring.\n\n## Resolving conflicting snapshot comparison via `*.snap.debug`\n\nOften your IDE has an excellent file comparison tool.\n\n- A `*.snap.debug` file will be created alongside your `*.snap` file when a conflict occurs.\n- You can then use your IDE tooling to compare the two files.\n- `*.snap.debug` is deleted automatically once the test passes.\n\n**Note:** `*.snap.debug` files should never be checked into version control so consider adding it to your `.gitignore`\n\n## snapshot.properties (required as of v2.4.0)\n\nThis file allows you to conveniently setup global defaults\n\n|     key          |  Description                                                                                                                                                       |\n|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|serializer        | Class name of the [serializer](#supplying-a-custom-snapshotserializer), default serializer                                                                         |\n|serializer.{name} | Class name of the [serializer](#supplying-a-custom-snapshotserializer), accessible via `.serializer(\"{name}\")`                                                     |\n|comparator        | Class name of the [comparator](#supplying-a-custom-snapshotcomparator)                                                                                             |\n|comparator.{name} | Class name of the [comparator](#supplying-a-custom-snapshotcomparator), accessible via `.comparator(\"{name}\")`                                                     |\n|reporters         | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter)                                                                    |\n|reporters.{name}  | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter), accessible via `.reporters(\"{name}\")`                             |\n|snapshot-dir      | Name of sub-folder holding your snapshots                                                                                                                          |\n|output-dir        | Base directory of your test files (although it can be a different directory if you want)                                                                           |\n|ci-env-var        | Name of environment variable used to detect if we are running on a Build Server                                                                                    |\n|update-snapshot   | Similar to `--updateSnapshot` in [Jest](https://jestjs.io/docs/en/snapshot-testing#updating-snapshots) \u003cbr/\u003e[all]=update all snapshots\u003cbr/\u003e[none]=update no snapshots\u003cbr/\u003e[MyTest1,MyTest2]=update snapshots in these classes only |\n\nFor example:\n\n ```text\nserializer=au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer\nserializer.base64=au.com.origin.snapshots.serializers.v1.Base64SnapshotSerializer\nserializer.json=au.com.origin.snapshots.jackson.serializers.v1.JacksonSnapshotSerializer\nserializer.orderedJson=au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer\ncomparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator\nreporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter\nsnapshot-dir=__snapshots__\noutput-dir=src/test/java\nci-env-var=CI\nupdate-snapshot=none\n```\n\n## Parameterized tests\n\nIn cases where the same test runs multiple times with different parameters you need to set the `scenario` and it must be\nunique for each run\n\n```java\nexpect.scenario(params).toMatchSnapshot(\"Something\");\n```\n\n## Scenario Example\n\n```groovy\npackage au.com.origin.snapshots.docs\n\nimport au.com.origin.snapshots.Expect\nimport au.com.origin.snapshots.annotations.SnapshotName\nimport au.com.origin.snapshots.spock.EnableSnapshots\nimport spock.lang.Specification\n\n@EnableSnapshots\nclass SpockWithParametersExample extends Specification {\n\n    private Expect expect\n\n    @SnapshotName(\"convert_to_uppercase\")\n    def 'Convert #scenario to uppercase'() {\n        when: 'I convert to uppercase'\n        String result = value.toUpperCase();\n        then: 'Should convert letters to uppercase'\n        // Check you snapshot against your output using a unique scenario\n        expect.scenario(scenario).toMatchSnapshot(result)\n        where:\n        scenario | value\n        'letter' | 'a'\n        'number' | '1'\n    }\n}\n```\n\n## Supplying a custom SnapshotSerializer\n\nThe serializer determines how a class gets converted into a string.\n\nSerializers are pluggable, so you can write you own by implementing the `SnapshotSerializer` interface.\n\nCurrently, we support the following serializers. \n\n### Shipped with core\n\n| Serializer                             | Description                                                                                                                 |\n|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|\n| ToStringSnapshotSerializer             | uses the `toString()` method                                                                                                | \n| Base64SnapshotSerializer               | use for images or other binary sources that output a `byte[]`. The output is encoded to Base64                             |\n\n### Shipped with Jackson plugin\n\n| Serializer                             | Description                                                                                                                 |\n|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|\n| JacksonSnapshotSerializer              | uses [jackson](https://github.com/FasterXML/jackson) to convert a class to a snapshot                                       |\n| DeterministicJacksonSnapshotSerializer | extension of JacksonSnapshotSerializer that also orders Collections for situations where the order changes on multiple runs | \n\nSerializers are resolved in the following order.\n\n- (method level) explicitly `expect.serializer(ToStringSerializer.class).toMatchSnapshot(...);` or via property\n  file `expect.serializer(\"json\").toMatchSnapshot(...);`\n- (class level) explicitly `@UseSnapshotConfig` which gets read from the `getSerializer()` method\n- (properties) implicitly via `snapshot.properties`\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Expect;\nimport au.com.origin.snapshots.annotations.UseSnapshotConfig;\nimport au.com.origin.snapshots.junit5.SnapshotExtension;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\n@ExtendWith(SnapshotExtension.class)\n@UseSnapshotConfig(LowercaseToStringSnapshotConfig.class)\npublic class JUnit5ResolutionHierarchyExample {\n\n    private Expect expect;\n\n    @Test\n    public void aliasMethodTest() {\n        expect\n                .serializer(\"json\") // \u003c------ Using snapshot.properties\n                .toMatchSnapshot(new TestObject());\n    }\n\n    @Test\n    public void customSerializerTest() {\n        expect\n                .serializer(UppercaseToStringSerializer.class)  // \u003c------ Using custom serializer\n                .toMatchSnapshot(new TestObject());\n    }\n\n    // Read from LowercaseToStringSnapshotConfig defined on the class\n    @Test\n    public void lowercaseTest() {\n        expect.toMatchSnapshot(new TestObject());\n    }\n}\n```\n\n### Example: HibernateSerializer\n\nSometimes the default serialization doesn't work for you. An example is Hibernate serialization where you get infinite\nrecursion on Lists/Sets.\n\nYou can supply any serializer you like Gson, Jackson or something else.\n\nFor example, the following will exclude the rendering of Lists without changing the source code to include `@JsonIgnore`\n. This is good because you shouldn't need to add annotations to your source code for testing purposes only.\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializer;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonIgnoreType;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Set;\n\npublic class HibernateSnapshotSerializer extends DeterministicJacksonSnapshotSerializer {\n\n    @Override\n    public void configure(ObjectMapper objectMapper) {\n        super.configure(objectMapper);\n\n        // Ignore Hibernate Lists to prevent infinite recursion\n        objectMapper.addMixIn(List.class, IgnoreTypeMixin.class);\n        objectMapper.addMixIn(Set.class, IgnoreTypeMixin.class);\n\n        // Ignore Fields that Hibernate generates for us automatically\n        objectMapper.addMixIn(BaseEntity.class, IgnoreHibernateEntityFields.class);\n    }\n\n    @JsonIgnoreType\n    class IgnoreTypeMixin {\n    }\n\n    abstract class IgnoreHibernateEntityFields {\n        @JsonIgnore\n        abstract Long getId();\n\n        @JsonIgnore\n        abstract Instant getCreatedDate();\n\n        @JsonIgnore\n        abstract Instant getLastModifiedDate();\n    }\n}\n```\n\n## Supplying a custom SnapshotComparator\n\nThe comparator determines if two snapshots match.\n\nCurrently, we support one default comparator (`PlainTextEqualsComparator`) which uses string equals for comparison.\n\nThis should work for most cases. Custom implementations of `SnapshotComparator` can provide more advanced comparisons.\n\nComparators follow the same resolution order as Serializers\n\n1. method\n1. class\n1. snapshot.properties\n\n### Example: JsonObjectComparator\n\nThe default comparator may be too strict for certain types of data. For example, when comparing json objects, formatting\nof the json string or the order of fields may not be of much importance during comparison. A custom comparator can help\nin such cases.\n\nFor example, the following will convert a json string to a Map and then perform an equals comparison so that formatting\nand field order are ignored.\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Snapshot;\nimport au.com.origin.snapshots.comparators.SnapshotComparator;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport lombok.SneakyThrows;\n\npublic class JsonObjectComparator implements SnapshotComparator {\n    @Override\n    public boolean matches(Snapshot previous, Snapshot current) {\n        return asObject(previous.getName(), previous.getBody()).equals(asObject(current.getName(), current.getBody()));\n    }\n\n    @SneakyThrows\n    private static Object asObject(String snapshotName, String json) {\n        return new ObjectMapper().readValue(json.replaceFirst(snapshotName + \"=\", \"\"), Object.class);\n    }\n}\n```\n\n## Supplying a custom SnapshotReporter\n\nThe reporter reports the details of comparison failures.\n\nCurrently, we support one default reporter (`PlainTextSnapshotReporter`) which uses assertj's DiffUtils to generate a\npatch of the differences between two snapshots.\n\nCustom reporters can be plugged in by implementing `SnapshotReporter`.\n\nReporters follow the same resolution order as Serializers and Comparators\n\n### Example: JsonDiffReporter\n\nFor generating and reporting json diffs using other libraries like https://github.com/skyscreamer/JSONassert\na custom reporter can be created like the one below.\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Snapshot;\nimport au.com.origin.snapshots.reporters.SnapshotReporter;\nimport au.com.origin.snapshots.serializers.SerializerType;\nimport lombok.SneakyThrows;\nimport org.skyscreamer.jsonassert.JSONAssert;\nimport org.skyscreamer.jsonassert.JSONCompareMode;\n\npublic class JsonAssertReporter implements SnapshotReporter {\n    @Override\n    public boolean supportsFormat(String outputFormat) {\n        return SerializerType.JSON.name().equalsIgnoreCase(outputFormat);\n    }\n\n    @Override\n    @SneakyThrows\n    public void report(Snapshot previous, Snapshot current) {\n        JSONAssert.assertEquals(previous.getBody(), current.getBody(), JSONCompareMode.STRICT);\n    }\n}\n```\n\n## Snapshot Headers\nYou can add metadata to your snapshots via headers. Headers can be used by Serializers, Comparators \u0026 Reporters\nto help interrogate the snapshot.\n\nCustom Serializers can also inject default headers as needed.\n\nExample of injecting a header manually\n```java\nString obj = \"hello\"\nexpect\n    .header(\"className\", obj.getClass().getName())\n    .header(\"foo\", \"bar\")\n    .toMatchSnapshot(obj);\n```\n\nSnapshot output\n```text\nau.com.origin.snapshots.SnapshotHeaders.canAddHeaders={\n  \"className\": \"java.lang.String\",\n  \"foo\": \"bar\"\n}[\nhello\n]\n```\n\n## Supplying a custom SnapshotConfig\n\nYou can override the snapshot configuration easily using the `@UseSnapshotConfig` annotation\n\n**JUnit5 Example**\n\n```java\npackage au.com.origin.snapshots.docs;\n\nimport au.com.origin.snapshots.Expect;\nimport au.com.origin.snapshots.annotations.UseSnapshotConfig;\nimport au.com.origin.snapshots.junit5.SnapshotExtension;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\n@ExtendWith(SnapshotExtension.class)\n@UseSnapshotConfig(LowercaseToStringSnapshotConfig.class)\npublic class JUnit5ResolutionHierarchyExample {\n\n    private Expect expect;\n\n    @Test\n    public void aliasMethodTest() {\n        expect\n                .serializer(\"json\") // \u003c------ Using snapshot.properties\n                .toMatchSnapshot(new TestObject());\n    }\n\n    @Test\n    public void customSerializerTest() {\n        expect\n                .serializer(UppercaseToStringSerializer.class)  // \u003c------ Using custom serializer\n                .toMatchSnapshot(new TestObject());\n    }\n\n    // Read from LowercaseToStringSnapshotConfig defined on the class\n    @Test\n    public void lowercaseTest() {\n        expect.toMatchSnapshot(new TestObject());\n    }\n}\n```\n\n# Troubleshooting\n\n**I'm seeing this error in my logs**\n\n```\norg/slf4j/LoggerFactory\njava.lang.NoClassDefFoundError: org/slf4j/LoggerFactory\n```\n\nSolution:\nAdd an SLF4J Provider such as`testImplementation(\"org.slf4j:slf4j-simple:2.0.0-alpha0\")`\n\n**My test source files are not in `src/test/java`**\n\nSolution: Override `output-dir` in `snapshot.properties`\n\n**I see the following error in JSON snapshots `java.lang.NoSuchFieldError: BINARY`**\n\nSolution: This happened to me in a spring-boot app, I removed my jackson dependencies and relied on the ones from\nspring-boot instead.\n\n# Contributing\n\nsee `CONTRIBUTING.md`\n","funding_links":[],"categories":["测试"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forigin-energy%2Fjava-snapshot-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forigin-energy%2Fjava-snapshot-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forigin-energy%2Fjava-snapshot-testing/lists"}