├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── codeql.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── LATEST_SNAPSHOT.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── publishing.gradle ├── spotless-groovy.gradle ├── spotless.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java-snapshot-testing-core ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ ├── Expect.java │ │ ├── Snapshot.java │ │ ├── SnapshotContext.java │ │ ├── SnapshotFile.java │ │ ├── SnapshotHeader.java │ │ ├── SnapshotProperties.java │ │ ├── SnapshotSerializerContext.java │ │ ├── SnapshotVerifier.java │ │ ├── annotations │ │ ├── SnapshotName.java │ │ └── UseSnapshotConfig.java │ │ ├── comparators │ │ ├── PlainTextEqualsComparator.java │ │ ├── SnapshotComparator.java │ │ └── v1 │ │ │ └── PlainTextEqualsComparator.java │ │ ├── config │ │ ├── PropertyResolvingSnapshotConfig.java │ │ ├── SnapshotConfig.java │ │ └── SnapshotConfigInjector.java │ │ ├── exceptions │ │ ├── LogGithubIssueException.java │ │ ├── MissingSnapshotPropertiesKeyException.java │ │ ├── ReservedWordException.java │ │ ├── SnapshotExtensionException.java │ │ └── SnapshotMatchException.java │ │ ├── logging │ │ └── LoggingHelper.java │ │ ├── reporters │ │ ├── PlainTextSnapshotReporter.java │ │ ├── SnapshotReporter.java │ │ └── v1 │ │ │ └── PlainTextSnapshotReporter.java │ │ ├── serializers │ │ ├── Base64SnapshotSerializer.java │ │ ├── SerializerType.java │ │ ├── SnapshotSerializer.java │ │ ├── ToStringSnapshotSerializer.java │ │ └── v1 │ │ │ ├── Base64SnapshotSerializer.java │ │ │ └── ToStringSnapshotSerializer.java │ │ └── utils │ │ └── ReflectionUtils.java │ └── test │ ├── java │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ ├── CustomFolderSnapshotTest.java │ │ ├── DebugSnapshotLineEndingsTest.java │ │ ├── DebugSnapshotTest.java │ │ ├── EmptySnapshotFileTest.java │ │ ├── EqualDebugSnapshotFileTest.java │ │ ├── FakeObject.java │ │ ├── NoNameChangeTest.java │ │ ├── OnLoadSnapshotFileTest.java │ │ ├── OrphanSnapshotTest.java │ │ ├── PrivateCalledMethodTest.java │ │ ├── ReflectionUtilities.java │ │ ├── ScenarioTest.java │ │ ├── SnapshotCaptor.java │ │ ├── SnapshotContextTest.java │ │ ├── SnapshotHeaders.java │ │ ├── SnapshotIntegrationTest.java │ │ ├── SnapshotMatcherScenarioTest.java │ │ ├── SnapshotMatcherTest.java │ │ ├── SnapshotNameAnnotationTest.java │ │ ├── SnapshotNameAnnotationWithDuplicatesTest.java │ │ ├── SnapshotOverrideClassTest.java │ │ ├── SnapshotSuperClassTest.java │ │ ├── SnapshotTest.java │ │ ├── SnapshotUtils.java │ │ ├── SnapshotUtilsTest.java │ │ ├── UpdateSnapshotPropertyTest.java │ │ ├── UpdateSnapshotTest.java │ │ ├── UseCustomConfigTest.java │ │ ├── UseCustomSerializerTest.java │ │ ├── comparators │ │ └── PlainTextEqualsComparatorTest.java │ │ ├── config │ │ ├── BaseSnapshotConfig.java │ │ └── ToStringSnapshotConfig.java │ │ ├── existing-snapshots │ │ └── __snapshots__ │ │ │ ├── DebugSnapshotLineEndingsTest.snap │ │ │ ├── DebugSnapshotTest.snap │ │ │ ├── EmptySnapshotFileTest$NestedClass.snap │ │ │ ├── EmptySnapshotFileTest$NestedClass.snap.debug │ │ │ ├── EmptySnapshotFileTest.snap │ │ │ ├── EmptySnapshotFileTest.snap.debug │ │ │ ├── EqualDebugSnapshotFileTest.snap │ │ │ ├── EqualDebugSnapshotFileTest.snap.debug │ │ │ ├── OrphanSnapshotTest.snap │ │ │ ├── PrivateCalledMethodTest.snap │ │ │ ├── README.md │ │ │ ├── ScenarioTest.snap │ │ │ ├── SnapshotHeaders.snap │ │ │ ├── SnapshotIntegrationTest.snap │ │ │ ├── SnapshotNameAnnotationTest.snap │ │ │ ├── SnapshotOverrideClassTest.snap │ │ │ ├── SnapshotUtilsTest.snap │ │ │ ├── UpdateSnapshotPropertyTest.snap │ │ │ ├── UseCustomConfigTest.snap │ │ │ └── UseCustomSerializerTest.snap │ │ ├── reporters │ │ └── PlainTextSnapshotReporterTest.java │ │ └── serializers │ │ ├── Base64SnapshotSerializerTest.java │ │ ├── LowercaseToStringSerializer.java │ │ ├── ToStringSnapshotSerializerTest.java │ │ └── UppercaseToStringSerializer.java │ └── resources │ ├── origin-logo.png │ └── snapshot.properties ├── java-snapshot-testing-junit4 ├── build.gradle └── src │ ├── main │ └── java │ │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ └── junit4 │ │ ├── SharedSnapshotHelpers.java │ │ ├── SnapshotClassRule.java │ │ ├── SnapshotRule.java │ │ └── SnapshotRunner.java │ └── test │ ├── java │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ ├── BaseClassTest.java │ │ ├── MyTest.java │ │ ├── ParameterizedTest.java │ │ ├── SnapshotRuleUsedTest.java │ │ ├── SnapshotRunnerUsedTest.java │ │ ├── __snapshots__ │ │ ├── BaseClassTest$NestedClass.snap │ │ ├── MyTest.snap │ │ ├── ParameterizedTest.snap │ │ ├── SnapshotContextRuleUsedTest.snap │ │ ├── SnapshotRuleUsedTest.snap │ │ └── SnapshotRunnerUsedTest.snap │ │ └── docs │ │ ├── JUnit4Example.java │ │ ├── JUnit4RulesExample.java │ │ └── __snapshots__ │ │ ├── JUnit4Example.snap │ │ └── JUnit4RulesExample.snap │ └── resources │ └── snapshot.properties ├── java-snapshot-testing-junit5 ├── build.gradle └── src │ ├── main │ └── java │ │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ └── junit5 │ │ └── SnapshotExtension.java │ └── test │ ├── java │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ ├── BaseClassTest.java │ │ ├── NestedClassTest.java │ │ ├── NestedClassTestWithExtends.java │ │ ├── SnapshotExtensionUsedTest.java │ │ ├── SnapshotParameterTest.java │ │ ├── __snapshots__ │ │ ├── BaseClassTest$NestedClass.snap │ │ ├── NestedClassTest$NestedClassWithExpectArgument.snap │ │ ├── NestedClassTest$NestedClassWithExpectInstance.snap │ │ ├── NestedClassTest$NestedClassWithoutSnapshot.snap │ │ ├── NestedClassTestWithExtends$NestedClass.snap │ │ ├── SnapshotExtensionUsedTest.snap │ │ └── SnapshotParameterTest.snap │ │ └── docs │ │ ├── CustomFrameworkExample.java │ │ ├── CustomSnapshotConfigExample.java │ │ ├── JUnit5Example.java │ │ ├── JUnit5ResolutionHierarchyExample.java │ │ ├── LowercaseToStringSerializer.java │ │ ├── LowercaseToStringSnapshotConfig.java │ │ ├── MyFirstSnapshotTest.java │ │ ├── TestObject.java │ │ ├── UppercaseToStringSerializer.java │ │ └── __snapshots__ │ │ ├── CustomFrameworkExample.snap │ │ ├── CustomSnapshotConfigExample.snap │ │ ├── CustomSnapshotContextConfigExample.snap │ │ ├── JUnit5Example.snap │ │ ├── JUnit5ResolutionHierarchyExample.snap │ │ ├── MyFirstSnapshotContextTest.snap │ │ └── MyFirstSnapshotTest.snap │ └── resources │ ├── junit-platform.properties │ └── snapshot.properties ├── java-snapshot-testing-plugin-jackson ├── build.gradle └── src │ ├── main │ └── java │ │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ └── jackson │ │ └── serializers │ │ ├── DeterministicCollectionModule.java │ │ ├── DeterministicJacksonSnapshotSerializer.java │ │ ├── JacksonSnapshotSerializer.java │ │ └── v1 │ │ ├── DeterministicJacksonSnapshotSerializer.java │ │ ├── JacksonSnapshotSerializer.java │ │ └── SnapshotPrettyPrinter.java │ └── test │ ├── java │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ └── jackson │ │ ├── NoNameChangeTest.java │ │ ├── ReflectionUtilities.java │ │ ├── docs │ │ ├── BaseEntity.java │ │ ├── CustomSerializerTest.java │ │ ├── HibernateSnapshotSerializer.java │ │ ├── JsonAssertReporter.java │ │ ├── JsonObjectComparator.java │ │ └── __snapshots__ │ │ │ └── CustomSerializerTest.snap │ │ └── serializers │ │ ├── DeterministicJacksonSnapshotSerializerTest.java │ │ ├── JacksonSnapshotSerializerTest.java │ │ └── __snapshots__ │ │ ├── DeterministicJacksonSnapshotSerializerTest.snap │ │ └── JacksonSnapshotSerializerTest.snap │ └── resources │ └── snapshot.properties ├── java-snapshot-testing-spock ├── build.gradle └── src │ ├── main │ └── groovy │ │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ └── spock │ │ ├── EnableSnapshots.groovy │ │ ├── SnapshotExtension.groovy │ │ └── SnapshotMethodInterceptor.groovy │ └── test │ ├── groovy │ └── au │ │ └── com │ │ └── origin │ │ └── snapshots │ │ ├── SpecificationBase.groovy │ │ ├── SpockExtensionUsedSpec.groovy │ │ ├── TestBaseSpec.groovy │ │ ├── __snapshots__ │ │ ├── SpockExtensionUsedSpec.snap │ │ └── TestBaseSpec.snap │ │ └── docs │ │ ├── SpockExample.groovy │ │ ├── SpockWithParametersExample.groovy │ │ └── __snapshots__ │ │ ├── SpockExample.snap │ │ └── SpockWithParametersExample.snap │ └── resources │ └── snapshot.properties └── settings.gradle /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2, 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "friday" 8 | assignees: 9 | - "jackmatt2" 10 | labels: 11 | - "dependencies" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - 'release/**' 11 | pull_request: 12 | branches: 13 | - master 14 | - 'release/**' 15 | workflow_dispatch: 16 | 17 | jobs: 18 | build: 19 | name: Build 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | java_version: [ '8' ] 24 | os: [ ubuntu-latest, windows-latest, macOS-latest ] 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Set up JDK ${{ matrix.java_version }} 28 | uses: actions/setup-java@v1 29 | with: 30 | java-version: ${{ matrix.java_version }} 31 | - name: Grant execute permission for gradlew 32 | run: chmod +x gradlew 33 | - name: Build with Gradle 34 | run: ./gradlew build 35 | deploy: 36 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 37 | name: Deploy SNAPSHOT 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Set up JDK 8 42 | uses: actions/setup-java@v1 43 | with: 44 | java-version: 8 45 | - name: Grant execute permission for gradlew 46 | run: chmod +x gradlew 47 | - name: Jar 48 | run: ./gradlew shadowJar 49 | - name: Generate tag version 50 | uses: anothrNick/github-tag-action@v1 51 | id: tag_version_dry_run 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | WITH_V: false 55 | DRY_RUN: true 56 | - name: publish 57 | env: 58 | SNAPSHOT_VERSION: ${{ steps.tag_version_dry_run.outputs.tag }}-SNAPSHOT 59 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY_ASCII_ARMOR }} 60 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_KEY_PASSPHRASE }} 61 | MAVEN_CENTRAL_TOKEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }} 62 | MAVEN_CENTRAL_TOKEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }} 63 | run: ./gradlew signArchives uploadArchives -Pversion=$SNAPSHOT_VERSION -PossrhUsername=${MAVEN_CENTRAL_TOKEN_USERNAME} -PossrhPassword=${MAVEN_CENTRAL_TOKEN_PASSWORD} -Psign=true 64 | 65 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '34 22 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | name: Release to Maven Central, Tag & Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Generate tag version 13 | uses: anothrNick/github-tag-action@v1 14 | id: tag_version_dry_run 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | WITH_V: false 18 | DRY_RUN: true 19 | - name: Set up JDK 8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 8 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Jar 26 | env: 27 | RELEASE_VERSION: ${{ steps.tag_version_dry_run.outputs.tag }} 28 | run: ./gradlew shadowJar -Pversion=$RELEASE_VERSION 29 | - name: Publish to Maven Central 30 | env: 31 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY_ASCII_ARMOR }} 32 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_KEY_PASSPHRASE }} 33 | MAVEN_CENTRAL_TOKEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }} 34 | MAVEN_CENTRAL_TOKEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }} 35 | RELEASE_VERSION: ${{ steps.tag_version_dry_run.outputs.tag }} 36 | run: ./gradlew -Pversion=$RELEASE_VERSION signArchives uploadArchives -PossrhUsername=${MAVEN_CENTRAL_TOKEN_USERNAME} -PossrhPassword=${MAVEN_CENTRAL_TOKEN_PASSWORD} -Psign=true 37 | - name: Close & Release Staging Repository 38 | env: 39 | MAVEN_CENTRAL_TOKEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }} 40 | MAVEN_CENTRAL_TOKEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }} 41 | run: ./gradlew closeAndReleaseRepository -PossrhUsername=${MAVEN_CENTRAL_TOKEN_USERNAME} -PossrhPassword=${MAVEN_CENTRAL_TOKEN_PASSWORD} 42 | - name: Push new tag 43 | uses: anothrNick/github-tag-action@v1 44 | id: tag_version 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | WITH_V: false 48 | DRY_RUN: false 49 | - name: Build Changelog 50 | id: github_release 51 | uses: mikepenz/release-changelog-builder-action@v3 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | - name: Create Github Release 55 | uses: softprops/action-gh-release@v1 56 | with: 57 | body: ${{steps.github_release.outputs.changelog}} 58 | tag_name: ${{ steps.tag_version.outputs.tag }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | 4 | # Ignore Gradle GUI config 5 | gradle-app.setting 6 | 7 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 8 | !gradle-wrapper.jar 9 | 10 | # Cache of project 11 | .gradletasknamecache 12 | 13 | # IDE 14 | .idea 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome contributions to this project by both internal and external parties 4 | 5 | ## How to contribute 6 | 7 | 1. Fork the repository into your own github account (external contributors) or create a new branch (internal 8 | contributions) 9 | 1. Make your code changes 10 | 1. Ensure you commit message is descriptive as it acts as the changelog. Mark any breaking changes with `BREAKING`. 11 | Include a rectification strategy if you introduce a `BREAKING` change. 12 | 1. Ensure `README.md` is updated if needed. All code samples in the README should have corresponding (actual sames) in the various docs/ folders 13 | 1. Submit a pull request back to `master` branch (or the branch you are contributing to) 14 | 1. Ensure Github Actions build passes 15 | 1. Await reviews 16 | 1. Once merged into `master` a `SNAPSHOT` build will be available for consumption 17 | immediately [here](https://oss.sonatype.org/content/repositories/snapshots/io/github/origin-energy/). Note that 18 | snapshots change regularly and cannot be relied upon. 19 | 1. Hard Releases will be made once enough features have been added. 20 | 21 | ## Building 22 | 23 | ```java 24 | ./gradlew spotlessApply shadowJar 25 | ``` 26 | 27 | ## Deploying locally and deploying to `.m2/` 28 | 29 | ``` 30 | ./gradlew publishToMavenLocal 31 | ``` 32 | 33 | Ensure you add `mavenLocal()` to your consuming project and the dependency verison matches that in your local `.m2` 34 | folder 35 | 36 | # Uploading to maven central 37 | see `.github/workflows/release.yml` 38 | 39 | # Uploading to maven central (manually) 40 | 41 | Gradle release plugin is not currently working so this is a manual process at the moment. 42 | 43 | # Setup GPG on your machine 44 | 45 | 1. copy the GPG Private key into a file `private.key` 46 | 1. run 47 | 48 | ``` 49 | gpg --import private.key 50 | cd ~/.gnupg 51 | gpg -k 52 | gpg --export-secret-key YOUR_KEY_ID > ~/.gnupg/secring.gpg 53 | ``` 54 | 55 | ## Preparing 56 | 57 | 1. Create a tag `X.X.X` 58 | 1. Update `gradle.properties` and remove `-SNAPSHOT` from the version number 59 | 1. Check this file into version control and push the branch to the remote 60 | 1. run 61 | 62 | ``` 63 | export SONAR_USERNAME=? 64 | export SONAR_PASSWORD=? 65 | export GPG_KEY_ID=? 66 | export GPG_KEY_PASSPHRASE=? 67 | export PATH_TO_SECRING_GPG=~/.gnupg/secring.gpg 68 | 69 | # I found shadowed classes are not included if you don't separate the gradle operations 70 | ./gradlew clean shadowJar 71 | ./gradlew signArchives uploadArchives -PossrhUsername=${SONAR_USERNAME} -PossrhPassword=${SONAR_PASSWORD} -Psigning.keyId=${GPG_KEY_ID} -Psigning.password=${GPG_KEY_PASSPHRASE} -Psigning.secretKeyRingFile=${PATH_TO_SECRING_GPG} 72 | ``` 73 | 74 | ## Releasing [Full Tutorial](https://central.sonatype.org/pages/ossrh-guide.html) 75 | 76 | 1. Login to SONAR (https://oss.sonatype.org) 77 | 1. Click 'Staging Repositories' and locate the 'iogithuborigin-energy' bundle 78 | 1. Review artifacts are correct in the 'Content' tab 79 | 1. Press the 'Close' and give a reason such as "Jack Matthews - Confirmed artifacts are OK" 80 | 1. Wait for about 1 min and press the 'Refresh button', if all sanity checks have passed the 'Release' button will be 81 | visible 82 | 1. Press the 'Release' button and give a reason for releasing 83 | 1. Objects should be available in about 10 min (Longer for search.maven.org) 84 | 85 | ## Cleanup 86 | 87 | 1. Checkout master branch 88 | 1. Increment version number in `gradle.properties` 89 | 1. Create pull request for merge -------------------------------------------------------------------------------- /LATEST_SNAPSHOT.md: -------------------------------------------------------------------------------- 1 | # Using and testing the latest SNAPSHOT (excuse the pun) from maven central 2 | 3 | Gradle 4 | 5 | ``` 6 | repositories { 7 | // ... 8 | maven { 9 | url "https://oss.sonatype.org/content/repositories/snapshots" 10 | } 11 | } 12 | 13 | dependencies { 14 | // ... 15 | 16 | // Replace {FRAMEWORK} with you testing framework 17 | // Replace {X.X.X} with the version number from `/gradle.properties` 18 | testCompile "io.github.origin-energy:java-snapshot-testing-{FRAMEWORK}:{X.X.X}-SNAPSHOT" 19 | } 20 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 André Bonna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.researchgate.release' version '2.6.0' apply false 3 | id 'com.github.johnrengelman.shadow' version '5.2.0' apply false 4 | id 'io.codearte.nexus-staging' version '0.22.0' 5 | id "com.diffplug.spotless" version "6.11.0" apply false 6 | } 7 | 8 | subprojects { subproject -> 9 | // FIXME this plugin is currently not working for multi-module projects even when defined at the top level only 10 | // Will need to release and TAG manually for now 11 | subproject.apply plugin: 'net.researchgate.release' 12 | subproject.apply plugin: 'java-library' 13 | subproject.apply plugin: 'com.github.johnrengelman.shadow' 14 | subproject.apply plugin: 'maven-publish' 15 | 16 | sourceCompatibility = '1.8' 17 | targetCompatibility = '1.8' 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | shadowJar { 24 | classifier = '' 25 | relocate 'org.assertj', 'shadow.org.assertj' 26 | relocate 'org.opentest4j', 'shadow.org.opentest4j' 27 | exclude "module-info.class" 28 | } 29 | 30 | dependencies { 31 | // Lombok 32 | compileOnly 'org.projectlombok:lombok:1.18.20' 33 | annotationProcessor 'org.projectlombok:lombok:1.18.20' 34 | testCompileOnly 'org.projectlombok:lombok:1.18.20' 35 | testAnnotationProcessor 'org.projectlombok:lombok:1.18.20' 36 | 37 | // Logging implementation 38 | compileOnly 'org.slf4j:slf4j-api:2.0.0-alpha0' 39 | } 40 | 41 | // Add verbose logging for Github Actions 42 | test { 43 | testLogging { 44 | events "passed", "skipped", "failed" 45 | exceptionFormat "full" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.github.origin-energy 2 | version=0.0.0-SNAPSHOT 3 | -------------------------------------------------------------------------------- /gradle/publishing.gradle: -------------------------------------------------------------------------------- 1 | task javadocJar(type: Jar) { 2 | archiveClassifier.set('javadoc') 3 | from javadoc 4 | } 5 | 6 | task sourcesJar(type: Jar) { 7 | archiveClassifier.set('sources') 8 | from sourceSets.main.allSource 9 | } 10 | 11 | artifacts { 12 | archives javadocJar, sourcesJar 13 | } 14 | 15 | apply plugin: 'net.researchgate.release' 16 | 17 | // Sign for maven central deployment 18 | if (project.hasProperty("sign")) { 19 | apply plugin: 'signing' 20 | signing { 21 | def signingKey = findProperty("signingKey") 22 | def signingPassword = findProperty("signingPassword") 23 | useInMemoryPgpKeys(signingKey, signingPassword) 24 | sign configurations.archives 25 | } 26 | 27 | // previously the plugin was using the `build` version which did not include the shadowed dependencies 28 | signArchives.dependsOn 'shadowJar' 29 | } 30 | 31 | // Maven Central Publishing 32 | if (project.hasProperty("ossrhUsername") && project.hasProperty("ossrhPassword")) { 33 | apply plugin: 'maven' 34 | 35 | nexusStaging { 36 | username project.property("ossrhUsername") 37 | password project.property("ossrhPassword") 38 | packageGroup 'io.github.origin-energy' 39 | } 40 | 41 | uploadArchives { 42 | repositories { 43 | mavenDeployer { 44 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 45 | 46 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 47 | authentication(userName: ossrhUsername, password: ossrhPassword) 48 | } 49 | 50 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 51 | authentication(userName: ossrhUsername, password: ossrhPassword) 52 | } 53 | 54 | pom.project { 55 | name 'java-snapsho-testing' 56 | packaging 'jar' 57 | description 'Snapshot Testing for Java' 58 | url 'https://github.com/origin-energy/java-snapshot-testing' 59 | scm { 60 | connection 'scm:git:https://github.com/origin-energy/java-snapshot-testing' 61 | url 'https://github.com/origin-energy/java-snapshot-testing' 62 | } 63 | 64 | licenses { 65 | license { 66 | name 'MIT License' 67 | url 'http://www.opensource.org/licenses/mit-license.php' 68 | } 69 | } 70 | 71 | developers { 72 | developer { 73 | id 'jack.matthews' 74 | name 'Jack Matthews' 75 | email 'jack.matthews@origin.com.au' 76 | } 77 | } 78 | } 79 | 80 | // We clear out all the dependencies because we have shadowed them inside the jar 81 | pom.whenConfigured { 82 | p -> p.dependencies = [] 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /gradle/spotless-groovy.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.diffplug.spotless' 2 | 3 | // Code formatting 4 | spotless { 5 | groovy { 6 | importOrder() 7 | excludeJava() 8 | greclipse() 9 | } 10 | } -------------------------------------------------------------------------------- /gradle/spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.diffplug.spotless' 2 | 3 | // Code formatting 4 | spotless { 5 | java { 6 | importOrder() 7 | removeUnusedImports() 8 | googleJavaFormat() 9 | formatAnnotations() 10 | } 11 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/.gitignore: -------------------------------------------------------------------------------- 1 | src/test/java/au/com/origin/snapshots/__snapshots__/ 2 | src/test/some-folder/ 3 | anyFilePath.debug 4 | blah -------------------------------------------------------------------------------- /java-snapshot-testing-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../gradle/publishing.gradle" 2 | apply from: "../gradle/spotless.gradle" 3 | 4 | dependencies { 5 | 6 | compileOnly 'com.fasterxml.jackson.core:jackson-core:2.11.3' 7 | compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.11.3' 8 | 9 | implementation 'org.assertj:assertj-core:3.11.1' 10 | implementation 'org.opentest4j:opentest4j:1.2.0' 11 | 12 | testImplementation 'org.slf4j:slf4j-simple:2.0.0-alpha0' 13 | testImplementation 'org.mockito:mockito-junit-jupiter:2.23.0' 14 | testImplementation 'org.mockito:mockito-core:2.23.4' 15 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' 16 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.3.2' 17 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.3.2' 18 | testImplementation group: 'commons-io', name: 'commons-io', version: '2.6' 19 | } 20 | 21 | test { useJUnitPlatform() } 22 | 23 | publishing { 24 | publications { 25 | myPublication(MavenPublication) { 26 | artifact shadowJar 27 | groupId 'io.github.origin-energy' 28 | artifactId 'java-snapshot-testing-core' 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/Snapshot.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.exceptions.LogGithubIssueException; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | import lombok.Builder; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.Getter; 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @EqualsAndHashCode 12 | @Builder 13 | @Getter 14 | @RequiredArgsConstructor 15 | public class Snapshot implements Comparable { 16 | 17 | private final String name; 18 | private final String scenario; 19 | private final SnapshotHeader header; 20 | private final String body; 21 | 22 | @Override 23 | public int compareTo(Snapshot other) { 24 | return (name + scenario).compareTo(other.name + other.scenario); 25 | } 26 | 27 | public String getIdentifier() { 28 | return scenario == null ? name : String.format("%s[%s]", name, scenario); 29 | } 30 | 31 | public static Snapshot parse(String rawText) { 32 | String regex = 33 | "^(?.*?)(\\[(?[^]]*)])?=(?
\\{[^}]*?})?(?(.*)$)"; 34 | Pattern p = Pattern.compile(regex, Pattern.DOTALL); 35 | Matcher m = p.matcher(rawText); 36 | boolean found = m.find(); 37 | if (!found) { 38 | throw new LogGithubIssueException( 39 | "Corrupt Snapshot (REGEX matches = 0): possibly due to manual editing or our REGEX failing\n" 40 | + "Possible Solutions\n" 41 | + "1. Ensure you have not accidentally manually edited the snapshot file!\n" 42 | + "2. Compare the snapshot with GIT history"); 43 | } 44 | 45 | String name = m.group("name"); 46 | String scenario = m.group("scenario"); 47 | String header = m.group("header"); 48 | String snapshot = m.group("snapshot"); 49 | 50 | if (name == null || snapshot == null) { 51 | throw new LogGithubIssueException( 52 | "Corrupt Snapshot (REGEX name or snapshot group missing): possibly due to manual editing or our REGEX failing\n" 53 | + "Possible Solutions\n" 54 | + "1. Ensure you have not accidentally manually edited the snapshot file\n" 55 | + "2. Compare the snapshot with your version control history"); 56 | } 57 | 58 | return Snapshot.builder() 59 | .name(name) 60 | .scenario(scenario) 61 | .header(SnapshotHeader.fromJson(header)) 62 | .body(snapshot) 63 | .build(); 64 | } 65 | 66 | /** 67 | * The raw string representation of the snapshot as it would appear in the *.snap file. 68 | * 69 | * @return raw snapshot 70 | */ 71 | public String raw() { 72 | String headerJson = (header == null) || (header.size() == 0) ? "" : header.toJson(); 73 | return getIdentifier() + "=" + headerJson + body; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return raw(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/SnapshotHeader.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.SneakyThrows; 9 | 10 | @RequiredArgsConstructor 11 | public class SnapshotHeader extends HashMap { 12 | 13 | // 14 | // Manual JSON serialization/deserialization as I don't want to 15 | // include another dependency for it 16 | // 17 | @SneakyThrows 18 | public String toJson() { 19 | StringBuilder b = new StringBuilder(); 20 | b.append("{\n"); 21 | final int lastIndex = this.size(); 22 | int currIndex = 0; 23 | for (Map.Entry entry : this.entrySet()) { 24 | currIndex++; 25 | String format = currIndex == lastIndex ? " \"%s\": \"%s\"\n" : " \"%s\": \"%s\",\n"; 26 | b.append(String.format(format, entry.getKey(), entry.getValue())); 27 | } 28 | b.append("}"); 29 | return b.toString(); 30 | } 31 | 32 | @SneakyThrows 33 | public static SnapshotHeader fromJson(String json) { 34 | SnapshotHeader snapshotHeader = new SnapshotHeader(); 35 | 36 | if (json == null) { 37 | return snapshotHeader; 38 | } 39 | 40 | String regex = "\\\"(?.*)\\\": \\\"(?.*)\\\""; 41 | Pattern p = Pattern.compile(regex); 42 | Matcher m = p.matcher(json); 43 | while (m.find()) { 44 | snapshotHeader.put(m.group("key"), m.group("value")); 45 | } 46 | return snapshotHeader; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/SnapshotProperties.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.exceptions.MissingSnapshotPropertiesKeyException; 4 | import java.io.InputStream; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Properties; 8 | import java.util.stream.Collectors; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | public enum SnapshotProperties { 13 | INSTANCE; 14 | 15 | Properties snapshotProperties = new Properties(); 16 | 17 | SnapshotProperties() { 18 | try { 19 | InputStream in = 20 | SnapshotProperties.class.getClassLoader().getResourceAsStream("snapshot.properties"); 21 | snapshotProperties.load(in); 22 | } catch (Exception e) { 23 | // It's ok, if the SnapshotConfig implementation attempts to get a property they will receive 24 | // a MissingSnapshotPropertiesKeyException 25 | } 26 | } 27 | 28 | public static String getOrThrow(String key) { 29 | Object value = INSTANCE.snapshotProperties.get(key); 30 | if (value == null) { 31 | throw new MissingSnapshotPropertiesKeyException(key); 32 | } 33 | return value.toString(); 34 | } 35 | 36 | public static T getInstance(String key) { 37 | String value = SnapshotProperties.getOrThrow(key); 38 | return createInstance(value); 39 | } 40 | 41 | public static List getInstances(String key) { 42 | String value = SnapshotProperties.getOrThrow(key); 43 | return Arrays.stream(value.split(",")) 44 | .map(String::trim) 45 | .map(it -> (T) createInstance(it)) 46 | .collect(Collectors.toList()); 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | private static T createInstance(String className) { 51 | try { 52 | Class clazz = Class.forName(className); 53 | return (T) clazz.newInstance(); 54 | } catch (Exception e) { 55 | throw new RuntimeException("Unable to instantiate class " + className, e); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/SnapshotSerializerContext.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName; 4 | import java.lang.reflect.Method; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * Contains details of the pending snapshot that can be modified in the Serializer prior to calling 11 | * toSnapshot(). 12 | */ 13 | @AllArgsConstructor 14 | public class SnapshotSerializerContext { 15 | 16 | @Getter @Setter private String name; 17 | 18 | @Getter @Setter private String scenario; 19 | 20 | @Getter @Setter private SnapshotHeader header; 21 | 22 | @Getter private Class testClass; 23 | 24 | @Getter private final Method testMethod; 25 | 26 | public static SnapshotSerializerContext from(SnapshotContext context) { 27 | SnapshotName snapshotName = context.getTestMethod().getAnnotation(SnapshotName.class); 28 | String name = 29 | snapshotName == null 30 | ? context.getTestClass().getName() + "." + context.getTestMethod().getName() 31 | : snapshotName.value(); 32 | return new SnapshotSerializerContext( 33 | name, 34 | context.getScenario(), 35 | context.getHeader(), 36 | context.getTestClass(), 37 | context.getTestMethod()); 38 | } 39 | 40 | public Snapshot toSnapshot(String body) { 41 | return Snapshot.builder().name(name).scenario(scenario).header(header).body(body).build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/annotations/SnapshotName.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Documented 6 | @Target({ElementType.METHOD}) 7 | @Inherited 8 | @Retention(RetentionPolicy.RUNTIME) 9 | public @interface SnapshotName { 10 | String value(); 11 | } 12 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/annotations/UseSnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.annotations; 2 | 3 | import au.com.origin.snapshots.config.SnapshotConfig; 4 | import java.lang.annotation.*; 5 | 6 | @Documented 7 | @Target({ElementType.TYPE}) 8 | @Inherited 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface UseSnapshotConfig { 11 | Class value(); 12 | } 13 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/comparators/PlainTextEqualsComparator.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.comparators; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | @Deprecated 7 | @Slf4j 8 | public class PlainTextEqualsComparator 9 | extends au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator { 10 | 11 | public PlainTextEqualsComparator() { 12 | super(); 13 | LoggingHelper.deprecatedV5( 14 | log, 15 | "Update to `au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator` in `snapshot.properties`"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/comparators/SnapshotComparator.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.comparators; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | 5 | public interface SnapshotComparator { 6 | boolean matches(Snapshot previous, Snapshot current); 7 | } 8 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/comparators/v1/PlainTextEqualsComparator.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.comparators.v1; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.comparators.SnapshotComparator; 5 | 6 | public class PlainTextEqualsComparator implements SnapshotComparator { 7 | 8 | @Override 9 | public boolean matches(Snapshot previous, Snapshot current) { 10 | return previous.getBody().equals(current.getBody()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/config/PropertyResolvingSnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.config; 2 | 3 | import au.com.origin.snapshots.SnapshotProperties; 4 | import au.com.origin.snapshots.comparators.SnapshotComparator; 5 | import au.com.origin.snapshots.exceptions.MissingSnapshotPropertiesKeyException; 6 | import au.com.origin.snapshots.logging.LoggingHelper; 7 | import au.com.origin.snapshots.reporters.SnapshotReporter; 8 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | @Slf4j 14 | public class PropertyResolvingSnapshotConfig implements SnapshotConfig { 15 | 16 | @Override 17 | public String getOutputDir() { 18 | return SnapshotProperties.getOrThrow("output-dir"); 19 | } 20 | 21 | @Override 22 | public String getSnapshotDir() { 23 | return SnapshotProperties.getOrThrow("snapshot-dir"); 24 | } 25 | 26 | @Override 27 | public Optional updateSnapshot() { 28 | // This was the original way to update snapshots 29 | Optional legacyFlag = 30 | Optional.ofNullable(System.getProperty(JVM_UPDATE_SNAPSHOTS_PARAMETER)); 31 | if (legacyFlag.isPresent()) { 32 | LoggingHelper.deprecatedV5( 33 | log, 34 | "Passing -PupdateSnapshot will be removed in a future release. Consider using snapshot.properties 'update-snapshot' toggle instead"); 35 | if ("false".equals(legacyFlag.get())) { 36 | return Optional.empty(); 37 | } 38 | return legacyFlag; 39 | } 40 | 41 | try { 42 | String updateSnapshot = SnapshotProperties.getOrThrow("update-snapshot"); 43 | if ("all".equals(updateSnapshot)) { 44 | return Optional.of(""); 45 | } else if ("none".equals(updateSnapshot)) { 46 | return Optional.empty(); 47 | } 48 | return Optional.of(updateSnapshot); 49 | } catch (MissingSnapshotPropertiesKeyException ex) { 50 | LoggingHelper.deprecatedV5( 51 | log, 52 | "You do not have 'update-snapshot=none' defined in your snapshot.properties - consider adding it now"); 53 | return Optional.empty(); 54 | } 55 | } 56 | 57 | @Override 58 | public SnapshotSerializer getSerializer() { 59 | return SnapshotProperties.getInstance("serializer"); 60 | } 61 | 62 | @Override 63 | public SnapshotComparator getComparator() { 64 | return SnapshotProperties.getInstance("comparator"); 65 | } 66 | 67 | @Override 68 | public List getReporters() { 69 | return SnapshotProperties.getInstances("reporters"); 70 | } 71 | 72 | @Override 73 | public boolean isCI() { 74 | String envVariable = SnapshotProperties.getOrThrow("ci-env-var"); 75 | return System.getenv(envVariable) != null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/config/SnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.config; 2 | 3 | import au.com.origin.snapshots.comparators.SnapshotComparator; 4 | import au.com.origin.snapshots.reporters.SnapshotReporter; 5 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Snapshot Configuration ---------------------- 11 | * 12 | *

Implement this interface when integrating `java-snapshot-testing` with a custom testing 13 | * library 14 | */ 15 | public interface SnapshotConfig { 16 | @Deprecated String JVM_UPDATE_SNAPSHOTS_PARAMETER = "updateSnapshot"; 17 | 18 | /** 19 | * The base directory where files get written (excluding package directories) default: 20 | * "src/test/java" 21 | * 22 | *

You might want to override if you have tests under "src/test/integration" for example 23 | * 24 | * @return snapshot output folder 25 | */ 26 | String getOutputDir(); 27 | 28 | /** 29 | * Subdirectory to store snapshots in 30 | * 31 | * @return name of subdirectory 32 | */ 33 | String getSnapshotDir(); 34 | 35 | /** 36 | * Optional 37 | * 38 | * @return snapshots should be updated automatically without verification 39 | */ 40 | default Optional updateSnapshot() { 41 | return Optional.ofNullable(System.getProperty(JVM_UPDATE_SNAPSHOTS_PARAMETER)); 42 | } 43 | 44 | /** 45 | * Optional Override to supply your own custom serialization function 46 | * 47 | * @return custom serialization function 48 | */ 49 | SnapshotSerializer getSerializer(); 50 | 51 | /** 52 | * Optional Override to supply your own custom comparator function 53 | * 54 | * @return custom comparator function 55 | */ 56 | SnapshotComparator getComparator(); 57 | 58 | /** 59 | * Optional Override to supply your own custom reporter functions Reporters will run in the same 60 | * sequence as provided. Reporters should throw exceptions to indicate comparison failure. 61 | * Exceptions thrown from reporters are aggregated and reported together. Reporters that wish to 62 | * leverage IDE comparison tools can use standard assertion libraries like assertj, junit jupiter 63 | * assertions (or) opentest4j. 64 | * 65 | * @return custom reporter functions 66 | */ 67 | List getReporters(); 68 | 69 | /** 70 | * Optional This method is meant to detect if we're running on a CI environment. This is used to 71 | * determine the action to be taken when a snapshot is not found. 72 | * 73 | *

If this method returns false, meaning we're NOT running on a CI environment (probably a dev 74 | * machine), a new snapshot is created when not found. 75 | * 76 | *

If this method returns true, meaning we're running on a CI environment, no new snapshots are 77 | * created and an error is thrown instead to prevent tests from silently passing when snapshots 78 | * are not found. 79 | * 80 | *

Often to determine if running on a CI environment is to check for the presence of a 'CI' env 81 | * variable 82 | * 83 | * @return boolean indicating if we're running on a CI environment or not 84 | */ 85 | boolean isCI(); 86 | } 87 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/config/SnapshotConfigInjector.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.config; 2 | 3 | public interface SnapshotConfigInjector { 4 | SnapshotConfig getSnapshotConfig(); 5 | } 6 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/exceptions/LogGithubIssueException.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.exceptions; 2 | 3 | public class LogGithubIssueException extends RuntimeException { 4 | 5 | private static final String LOG_SUPPORT_TICKET = 6 | "\n\n*** This exception should never be thrown ***\n" 7 | + "Log a support ticket at https://github.com/origin-energy/java-snapshot-testing/issues with details of the exception\n"; 8 | 9 | public LogGithubIssueException(String message) { 10 | super(message + LOG_SUPPORT_TICKET); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/exceptions/MissingSnapshotPropertiesKeyException.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.exceptions; 2 | 3 | public class MissingSnapshotPropertiesKeyException extends RuntimeException { 4 | 5 | public MissingSnapshotPropertiesKeyException(String key) { 6 | super("\"snapshot.properties\" is missing required key=" + key); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/exceptions/ReservedWordException.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.exceptions; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | public class ReservedWordException extends RuntimeException { 7 | 8 | public ReservedWordException(String message) { 9 | super(message); 10 | } 11 | 12 | public ReservedWordException(String element, String reservedWord, List reservedWords) { 13 | super( 14 | String.format( 15 | "You cannot use the '%s' character inside '%s'. Reserved characters are ", 16 | reservedWord, element, reservedWords.stream().collect(Collectors.joining(",")))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/exceptions/SnapshotExtensionException.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.exceptions; 2 | 3 | public class SnapshotExtensionException extends RuntimeException { 4 | 5 | public SnapshotExtensionException(String message) { 6 | super(message); 7 | } 8 | 9 | public SnapshotExtensionException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/exceptions/SnapshotMatchException.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.exceptions; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import org.opentest4j.MultipleFailuresError; 6 | 7 | public class SnapshotMatchException extends MultipleFailuresError { 8 | 9 | public SnapshotMatchException(String message) { 10 | super(message, Collections.emptyList()); 11 | } 12 | 13 | public SnapshotMatchException(String message, Throwable cause) { 14 | super(message, Collections.singletonList(cause)); 15 | } 16 | 17 | public SnapshotMatchException(String message, List causes) { 18 | super(message, causes); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/logging/LoggingHelper.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.logging; 2 | 3 | import org.slf4j.Logger; 4 | 5 | public class LoggingHelper { 6 | 7 | public static void deprecatedV5(Logger log, String message) { 8 | log.warn( 9 | "\n\n** Deprecation Warning **\nThis feature will be removed in version 5.X\n" 10 | + message 11 | + "\n\n"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/reporters/PlainTextSnapshotReporter.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.reporters; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | @Deprecated 7 | @Slf4j 8 | public class PlainTextSnapshotReporter 9 | extends au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter { 10 | 11 | public PlainTextSnapshotReporter() { 12 | super(); 13 | LoggingHelper.deprecatedV5( 14 | log, 15 | "Update to `au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter` in `snapshot.properties`"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/reporters/SnapshotReporter.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.reporters; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | 5 | public interface SnapshotReporter { 6 | 7 | boolean supportsFormat(String outputFormat); 8 | 9 | void report(Snapshot previous, Snapshot current); 10 | } 11 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/reporters/v1/PlainTextSnapshotReporter.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.reporters.v1; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.reporters.SnapshotReporter; 5 | import java.util.Arrays; 6 | import java.util.function.Supplier; 7 | import org.assertj.core.util.diff.DiffUtils; 8 | import org.assertj.core.util.diff.Patch; 9 | import org.opentest4j.AssertionFailedError; 10 | 11 | public class PlainTextSnapshotReporter implements SnapshotReporter { 12 | 13 | private static final Supplier NO_DIFF_EXCEPTION_SUPPLIER = 14 | () -> 15 | new IllegalStateException( 16 | "No differences found. Potential mismatch between comparator and reporter"); 17 | 18 | public static String getDiffString(Patch patch) { 19 | return patch.getDeltas().stream() 20 | .map(delta -> delta.toString() + "\n") 21 | .reduce(String::concat) 22 | .orElseThrow(NO_DIFF_EXCEPTION_SUPPLIER); 23 | } 24 | 25 | @Override 26 | public boolean supportsFormat(String outputFormat) { 27 | return true; // always true 28 | } 29 | 30 | @Override 31 | public void report(Snapshot previous, Snapshot current) { 32 | Patch patch = 33 | DiffUtils.diff( 34 | Arrays.asList(previous.raw().split("\n")), Arrays.asList(current.raw().split("\n"))); 35 | 36 | String message = "Error on: \n" + current.raw() + "\n\n" + getDiffString(patch); 37 | 38 | throw new AssertionFailedError(message, previous.raw(), current.raw()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/Base64SnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | * This Serializer converts a byte[] into a base64 encoded string. If the input is not a byte[] it 8 | * will be converted using `.getBytes(StandardCharsets.UTF_8)` method 9 | */ 10 | @Slf4j 11 | @Deprecated 12 | public class Base64SnapshotSerializer 13 | extends au.com.origin.snapshots.serializers.v1.Base64SnapshotSerializer { 14 | public Base64SnapshotSerializer() { 15 | super(); 16 | LoggingHelper.deprecatedV5( 17 | log, 18 | "Update to `au.com.origin.snapshots.serializers.v1.Base64SnapshotSerializer` in `snapshot.properties`"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/SerializerType.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | public enum SerializerType { 4 | TEXT, 5 | JSON, 6 | BASE64; 7 | } 8 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/SnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import java.util.function.BiFunction; 6 | 7 | public interface SnapshotSerializer 8 | extends BiFunction { 9 | String getOutputFormat(); 10 | } 11 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/ToStringSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | * This Serializer does a snapshot of the {@link Object#toString()} method 8 | * 9 | *

Will render each toString() on a separate line 10 | */ 11 | @Slf4j 12 | @Deprecated 13 | public class ToStringSnapshotSerializer 14 | extends au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer { 15 | public ToStringSnapshotSerializer() { 16 | super(); 17 | LoggingHelper.deprecatedV5( 18 | log, 19 | "Update to `au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer` in `snapshot.properties`"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/v1/Base64SnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers.v1; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import au.com.origin.snapshots.serializers.SerializerType; 6 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Base64; 9 | 10 | /** 11 | * This Serializer converts a byte[] into a base64 encoded string. If the input is not a byte[] it 12 | * will be converted using `.getBytes(StandardCharsets.UTF_8)` method 13 | */ 14 | public class Base64SnapshotSerializer implements SnapshotSerializer { 15 | private static final ToStringSnapshotSerializer toStringSnapshotSerializer = 16 | new ToStringSnapshotSerializer(); 17 | 18 | @Override 19 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 20 | if (object == null) { 21 | toStringSnapshotSerializer.apply("", gen); 22 | } 23 | byte[] bytes = 24 | object instanceof byte[] 25 | ? (byte[]) object 26 | : object.toString().getBytes(StandardCharsets.UTF_8); 27 | String encoded = Base64.getEncoder().encodeToString(bytes); 28 | return toStringSnapshotSerializer.apply(encoded, gen); 29 | } 30 | 31 | @Override 32 | public String getOutputFormat() { 33 | return SerializerType.BASE64.name(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/serializers/v1/ToStringSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers.v1; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotFile; 5 | import au.com.origin.snapshots.SnapshotSerializerContext; 6 | import au.com.origin.snapshots.serializers.SerializerType; 7 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | /** 11 | * This Serializer does a snapshot of the {@link Object#toString()} method 12 | * 13 | *

Will render each toString() on a separate line 14 | */ 15 | @Slf4j 16 | public class ToStringSnapshotSerializer implements SnapshotSerializer { 17 | 18 | @Override 19 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 20 | String body = "[\n" + object.toString() + "\n]"; 21 | if (body.contains(SnapshotFile.SPLIT_STRING)) { 22 | log.warn( 23 | "Found 3 consecutive lines in your snapshot \\n\\n\\n. This sequence is reserved as the snapshot separator - replacing with \\n.\\n.\\n"); 24 | body = body.replaceAll(SnapshotFile.SPLIT_STRING, "\n.\n.\n"); 25 | } 26 | return gen.toSnapshot(body); 27 | } 28 | 29 | @Override 30 | public String getOutputFormat() { 31 | return SerializerType.TEXT.name(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.utils; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | import java.util.Optional; 6 | import java.util.function.Predicate; 7 | import lombok.experimental.UtilityClass; 8 | 9 | @UtilityClass 10 | public class ReflectionUtils { 11 | 12 | /** 13 | * Find {@link Field} by given predicate. 14 | * 15 | *

Invoke the given predicate on all fields in the target class, going up the class hierarchy 16 | * to get all declared fields. 17 | * 18 | * @param clazz the target class to analyze 19 | * @param predicate the predicate 20 | * @return the field or empty optional 21 | */ 22 | public static Optional findFieldByPredicate( 23 | final Class clazz, final Predicate predicate) { 24 | Class targetClass = clazz; 25 | 26 | do { 27 | final Field[] fields = targetClass.getDeclaredFields(); 28 | for (final Field field : fields) { 29 | if (!predicate.test(field)) { 30 | continue; 31 | } 32 | return Optional.of(field); 33 | } 34 | targetClass = targetClass.getSuperclass(); 35 | } while (targetClass != null && targetClass != Object.class); 36 | 37 | return Optional.empty(); 38 | } 39 | 40 | public static void makeAccessible(final Field field) { 41 | if ((!Modifier.isPublic(field.getModifiers()) 42 | || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) 43 | || Modifier.isFinal(field.getModifiers())) 44 | && !field.isAccessible()) { 45 | field.setAccessible(true); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/CustomFolderSnapshotTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import org.assertj.core.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.TestInfo; 12 | 13 | public class CustomFolderSnapshotTest { 14 | 15 | private static final String OUTPUT_FILE = 16 | "src/test/some-folder/au/com/origin/snapshots/__snapshots__/CustomFolderSnapshotTest.snap"; 17 | 18 | @BeforeEach 19 | public void beforeEach() throws IOException { 20 | Path path = Paths.get(OUTPUT_FILE); 21 | Files.deleteIfExists(path); 22 | } 23 | 24 | @Test 25 | void shouldBeAbleToChangeSnapshotFolder(TestInfo testInfo) { 26 | SnapshotVerifier snapshotVerifier = 27 | new SnapshotVerifier(new CustomFolderSnapshotConfig(), testInfo.getTestClass().get()); 28 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 29 | expect.toMatchSnapshot(FakeObject.builder().id("shouldBeAbleToChangeSnapshotFolder").build()); 30 | snapshotVerifier.validateSnapshots(); 31 | 32 | Path path = Paths.get(OUTPUT_FILE); 33 | Assertions.assertThat(Files.exists(path)); 34 | } 35 | 36 | @Test 37 | void shouldBeAbleToChangeSnapshotFolderLegacyTrailginSlash(TestInfo testInfo) { 38 | SnapshotVerifier snapshotVerifier = 39 | new SnapshotVerifier(new CustomFolderSnapshotConfigLegacy(), testInfo.getTestClass().get()); 40 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 41 | expect.toMatchSnapshot(FakeObject.builder().id("shouldBeAbleToChangeSnapshotFolder").build()); 42 | snapshotVerifier.validateSnapshots(); 43 | 44 | Path path = Paths.get(OUTPUT_FILE); 45 | Assertions.assertThat(Files.exists(path)); 46 | } 47 | 48 | public static class CustomFolderSnapshotConfig extends BaseSnapshotConfig { 49 | 50 | @Override 51 | public String getOutputDir() { 52 | return "src/test/some-folder"; 53 | } 54 | } 55 | 56 | public static class CustomFolderSnapshotConfigLegacy extends BaseSnapshotConfig { 57 | 58 | @Override 59 | public String getOutputDir() { 60 | return "src/test/some-folder/"; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/DebugSnapshotLineEndingsTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import au.com.origin.snapshots.config.SnapshotConfig; 7 | import au.com.origin.snapshots.serializers.SerializerType; 8 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | import lombok.SneakyThrows; 16 | import org.junit.jupiter.api.BeforeAll; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.DisplayName; 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.TestInfo; 21 | 22 | class DebugSnapshotLineEndingsTest { 23 | private static final SnapshotConfig CONFIG = 24 | new BaseSnapshotConfig() { 25 | @Override 26 | public SnapshotSerializer getSerializer() { 27 | return new MultiLineSnapshotSerializer(); 28 | } 29 | }; 30 | private static final String DEBUG_FILE_PATH = 31 | "src/test/java/au/com/origin/snapshots/__snapshots__/DebugSnapshotLineEndingsTest.snap.debug"; 32 | private static final String SNAPSHOT_FILE_PATH = 33 | "src/test/java/au/com/origin/snapshots/__snapshots__/DebugSnapshotLineEndingsTest.snap"; 34 | 35 | @BeforeAll 36 | static void beforeAll() { 37 | SnapshotUtils.copyTestSnapshots(); 38 | } 39 | 40 | @SneakyThrows 41 | @BeforeEach 42 | void beforeEach() { 43 | Files.deleteIfExists(Paths.get(DEBUG_FILE_PATH)); 44 | } 45 | 46 | /** 47 | * Scenario: - An existing snapshot file checked out from git will have CR LF line endings on 48 | * Windows OS - A newly created snapshot file will have LF line endings on any OS (see 49 | * MultiLineSnapshotSerializer) Expectation: - As snapshot file content is identical (except for 50 | * line endings), the debug file should not be created 51 | */ 52 | @DisplayName( 53 | "Debug file should not be created when snapshots match the existing snapshot regardless of line endings") 54 | @Test 55 | void existingSnapshotDifferentLineEndings(TestInfo testInfo) { 56 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH))); 57 | assertTrue(Files.notExists(Paths.get(DEBUG_FILE_PATH))); 58 | 59 | SnapshotVerifier snapshotVerifier = new SnapshotVerifier(CONFIG, testInfo.getTestClass().get()); 60 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 61 | expect.toMatchSnapshot(Arrays.asList("a", "b")); 62 | snapshotVerifier.validateSnapshots(); 63 | 64 | assertTrue(Files.notExists(Paths.get(DEBUG_FILE_PATH)), "Debug file should not be created"); 65 | } 66 | 67 | private static class MultiLineSnapshotSerializer implements SnapshotSerializer { 68 | @Override 69 | public String getOutputFormat() { 70 | return SerializerType.TEXT.name(); 71 | } 72 | 73 | @Override 74 | public Snapshot apply(Object object, SnapshotSerializerContext snapshotSerializerContext) { 75 | Object body = 76 | "[\n" 77 | + Arrays.asList(object).stream() 78 | .flatMap( 79 | o -> { 80 | if (o instanceof Collection) { 81 | return ((Collection) o).stream(); 82 | } 83 | return Stream.of(o); 84 | }) 85 | .map(Object::toString) 86 | .collect(Collectors.joining("\n")) 87 | + "\n]"; 88 | 89 | return Snapshot.builder() 90 | .name( 91 | "au.com.origin.snapshots.DebugSnapshotLineEndingsTest.existingSnapshotDifferentLineEndings") 92 | .body(body.toString()) 93 | .build(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/EmptySnapshotFileTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import au.com.origin.snapshots.config.ToStringSnapshotConfig; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import org.junit.jupiter.api.*; 9 | 10 | public class EmptySnapshotFileTest { 11 | 12 | private static final String SNAPSHOT_FILE_PATH = 13 | "src/test/java/au/com/origin/snapshots/__snapshots__/EmptySnapshotFileTest.snap"; 14 | private static final String DEBUG_FILE_PATH = 15 | "src/test/java/au/com/origin/snapshots/__snapshots__/EmptySnapshotFileTest.snap.debug"; 16 | 17 | @BeforeAll 18 | static void beforeAll() { 19 | SnapshotUtils.copyTestSnapshots(); 20 | } 21 | 22 | @DisplayName("Should remove empty snapshots") 23 | @Test 24 | public void shouldRemoveEmptySnapshots(TestInfo testInfo) { 25 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH))); 26 | assertTrue(Files.exists(Paths.get(DEBUG_FILE_PATH))); 27 | 28 | SnapshotVerifier snapshotVerifier = 29 | new SnapshotVerifier(new ToStringSnapshotConfig(), testInfo.getTestClass().get()); 30 | snapshotVerifier.validateSnapshots(); 31 | 32 | assertTrue(Files.notExists(Paths.get(SNAPSHOT_FILE_PATH))); 33 | assertTrue(Files.notExists(Paths.get(DEBUG_FILE_PATH))); 34 | } 35 | 36 | @Nested 37 | public class NestedClass { 38 | 39 | private static final String SNAPSHOT_FILE_PATH_NESTED = 40 | "src/test/java/au/com/origin/snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap"; 41 | private static final String DEBUG_FILE_PATH_NESTED = 42 | "src/test/java/au/com/origin/snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap.debug"; 43 | 44 | @DisplayName("Should remove empty nested snapshots") 45 | @Test 46 | public void shouldRemoveEmptyNestedSnapshots(TestInfo testInfo) { 47 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH_NESTED))); 48 | assertTrue(Files.exists(Paths.get(DEBUG_FILE_PATH_NESTED))); 49 | 50 | SnapshotVerifier snapshotVerifier = 51 | new SnapshotVerifier(new ToStringSnapshotConfig(), testInfo.getTestClass().get()); 52 | snapshotVerifier.validateSnapshots(); 53 | 54 | assertTrue(Files.notExists(Paths.get(SNAPSHOT_FILE_PATH_NESTED))); 55 | assertTrue(Files.notExists(Paths.get(DEBUG_FILE_PATH_NESTED))); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/EqualDebugSnapshotFileTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | 5 | import au.com.origin.snapshots.config.ToStringSnapshotConfig; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.TestInfo; 12 | 13 | public class EqualDebugSnapshotFileTest { 14 | 15 | private static final String SNAPSHOT_FILE_PATH = 16 | "src/test/java/au/com/origin/snapshots/__snapshots__/EqualDebugSnapshotFileTest.snap"; 17 | private static final String DEBUG_FILE_PATH = 18 | "src/test/java/au/com/origin/snapshots/__snapshots__/EqualDebugSnapshotFileTest.snap.debug"; 19 | 20 | @BeforeAll 21 | static void beforeAll() { 22 | SnapshotUtils.copyTestSnapshots(); 23 | } 24 | 25 | @DisplayName("Should remove equal debug snapshots") 26 | @Test 27 | public void shouldRemoveEmptySnapshots(TestInfo testInfo) { 28 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH))); 29 | assertTrue(Files.exists(Paths.get(DEBUG_FILE_PATH))); 30 | 31 | SnapshotVerifier snapshotVerifier = 32 | new SnapshotVerifier(new ToStringSnapshotConfig(), testInfo.getTestClass().get()); 33 | snapshotVerifier.validateSnapshots(); 34 | 35 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH))); 36 | assertTrue(Files.notExists(Paths.get(DEBUG_FILE_PATH))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/FakeObject.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import java.util.List; 4 | import lombok.*; 5 | 6 | @ToString 7 | @Builder 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class FakeObject { 11 | 12 | private String id; 13 | 14 | private Integer value; 15 | 16 | private String name; 17 | 18 | @Setter private FakeObject fakeObject; 19 | 20 | public void fakeMethod(String fakeName, Long fakeNumber, List fakeList) {} 21 | 22 | public void fakeMethodWithComplexObject(Object fakeObj) {} 23 | 24 | public void fakeMethodWithComplexFakeObject(FakeObject fakeObj) {} 25 | } 26 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/NoNameChangeTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.comparators.PlainTextEqualsComparator; 6 | import au.com.origin.snapshots.reporters.PlainTextSnapshotReporter; 7 | import au.com.origin.snapshots.serializers.Base64SnapshotSerializer; 8 | import au.com.origin.snapshots.serializers.SerializerType; 9 | import au.com.origin.snapshots.serializers.ToStringSnapshotSerializer; 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** 13 | * These classes are likely defined in snapshot.properties as a string. 14 | * 15 | *

The clients IDE will not complain if they change so ensure they don't 16 | */ 17 | public class NoNameChangeTest { 18 | 19 | @Test 20 | public void serializersApiShouldNotChange() { 21 | assertThat(Base64SnapshotSerializer.class.getName()) 22 | .isEqualTo("au.com.origin.snapshots.serializers.Base64SnapshotSerializer"); 23 | assertThat(ToStringSnapshotSerializer.class.getName()) 24 | .isEqualTo("au.com.origin.snapshots.serializers.ToStringSnapshotSerializer"); 25 | assertThat(SerializerType.class.getName()) 26 | .isEqualTo("au.com.origin.snapshots.serializers.SerializerType"); 27 | } 28 | 29 | @Test 30 | public void reportersApiShouldNotChange() { 31 | assertThat(PlainTextSnapshotReporter.class.getName()) 32 | .isEqualTo("au.com.origin.snapshots.reporters.PlainTextSnapshotReporter"); 33 | } 34 | 35 | @Test 36 | public void comparatorsApiShouldNotChange() { 37 | assertThat(PlainTextEqualsComparator.class.getName()) 38 | .isEqualTo("au.com.origin.snapshots.comparators.PlainTextEqualsComparator"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/OnLoadSnapshotFileTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 7 | import au.com.origin.snapshots.config.SnapshotConfig; 8 | import au.com.origin.snapshots.serializers.ToStringSnapshotSerializer; 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import org.junit.jupiter.api.BeforeAll; 16 | import org.junit.jupiter.api.DisplayName; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.api.TestInfo; 19 | 20 | public class OnLoadSnapshotFileTest { 21 | 22 | private static final String SNAPSHOT_FILE_PATH = 23 | "src/test/java/au/com/origin/snapshots/__snapshots__/OnLoadSnapshotFileTest.snap"; 24 | private final SnapshotConfig CUSTOM_SNAPSHOT_CONFIG = new BaseSnapshotConfig(); 25 | 26 | @BeforeAll 27 | static void beforeAll() throws IOException { 28 | Files.deleteIfExists(Paths.get(SNAPSHOT_FILE_PATH)); 29 | String snapshotFileContent = 30 | "au.com.origin.snapshots.OnLoadSnapshotFileTest.shouldLoadFileWithCorrectEncodingForCompare=[\n" 31 | + "any special characters that need correct encoding äöüèéàè\n" 32 | + "]"; 33 | createSnapshotFile(snapshotFileContent); 34 | } 35 | 36 | @DisplayName("Should load snapshots with correct encoding") 37 | @Test 38 | public void shouldLoadFileWithCorrectEncodingForCompare(TestInfo testInfo) throws IOException { 39 | assertTrue(Files.exists(Paths.get(SNAPSHOT_FILE_PATH))); 40 | 41 | SnapshotVerifier snapshotVerifier = 42 | new SnapshotVerifier(CUSTOM_SNAPSHOT_CONFIG, testInfo.getTestClass().get()); 43 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 44 | expect 45 | .serializer(ToStringSnapshotSerializer.class) 46 | .toMatchSnapshot("any special characters that need correct encoding äöüèéàè"); 47 | snapshotVerifier.validateSnapshots(); 48 | 49 | File f = new File(SNAPSHOT_FILE_PATH); 50 | assertThat(String.join("\n", Files.readAllLines(f.toPath()))) 51 | .isEqualTo( 52 | "au.com.origin.snapshots.OnLoadSnapshotFileTest.shouldLoadFileWithCorrectEncodingForCompare=[\n" 53 | + "any special characters that need correct encoding äöüèéàè\n" 54 | + "]"); 55 | } 56 | 57 | private static void createSnapshotFile(String snapshot) { 58 | try { 59 | File file = new File(SNAPSHOT_FILE_PATH); 60 | file.getParentFile().mkdirs(); 61 | file.createNewFile(); 62 | try (FileOutputStream fileStream = new FileOutputStream(file, false)) { 63 | fileStream.write(snapshot.getBytes(StandardCharsets.UTF_8)); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } catch (IOException e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/OrphanSnapshotTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 7 | import au.com.origin.snapshots.config.SnapshotConfig; 8 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import org.junit.jupiter.api.BeforeAll; 14 | import org.junit.jupiter.api.DisplayName; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestInfo; 17 | 18 | public class OrphanSnapshotTest { 19 | 20 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 21 | 22 | @BeforeAll 23 | static void beforeAll() { 24 | SnapshotUtils.copyTestSnapshots(); 25 | } 26 | 27 | @DisplayName("should fail the build when failOnOrphans=true") 28 | @Test 29 | void orphanSnapshotsShouldFailTheBuild(TestInfo testInfo) throws IOException { 30 | SnapshotVerifier snapshotVerifier = 31 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get(), true); 32 | FakeObject fakeObject1 = FakeObject.builder().id("anyId1").value(1).name("anyName1").build(); 33 | final Path snapshotFile = 34 | Paths.get("src/test/java/au/com/origin/snapshots/__snapshots__/OrphanSnapshotTest.snap"); 35 | 36 | long bytesBefore = Files.size(snapshotFile); 37 | 38 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 39 | expect.toMatchSnapshot(fakeObject1); 40 | 41 | Throwable exceptionThatWasThrown = 42 | assertThrows( 43 | SnapshotMatchException.class, 44 | () -> { 45 | snapshotVerifier.validateSnapshots(); 46 | }); 47 | 48 | assertThat(exceptionThatWasThrown.getMessage()).isEqualTo("ERROR: Found orphan snapshots"); 49 | 50 | // Ensure file has not changed 51 | long bytesAfter = Files.size(snapshotFile); 52 | assertThat(bytesAfter).isGreaterThan(bytesBefore); 53 | } 54 | 55 | @DisplayName("should not fail the build when failOnOrphans=false") 56 | @Test 57 | void orphanSnapshotsShouldNotFailTheBuild(TestInfo testInfo) throws IOException { 58 | SnapshotVerifier snapshotVerifier = 59 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get(), false); 60 | FakeObject fakeObject1 = FakeObject.builder().id("anyId1").value(1).name("anyName1").build(); 61 | final Path snapshotFile = 62 | Paths.get("src/test/java/au/com/origin/snapshots/__snapshots__/OrphanSnapshotTest.snap"); 63 | 64 | long bytesBefore = Files.size(snapshotFile); 65 | 66 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 67 | expect.toMatchSnapshot(fakeObject1); 68 | 69 | snapshotVerifier.validateSnapshots(); 70 | 71 | // Ensure file has not changed 72 | long bytesAfter = Files.size(snapshotFile); 73 | assertThat(bytesAfter).isGreaterThan(bytesBefore); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/PrivateCalledMethodTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.TestInfo; 6 | 7 | class PrivateCalledMethodTest { 8 | 9 | @Test 10 | void testName(TestInfo testInfo) { 11 | SnapshotVerifier snapshotVerifier = 12 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()); 13 | testBasedOnArgs("testContent", testInfo); 14 | snapshotVerifier.validateSnapshots(); 15 | } 16 | 17 | private void testBasedOnArgs(String arg, TestInfo testInfo) { 18 | SnapshotVerifier snapshotVerifier = 19 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()); 20 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 21 | expect.toMatchSnapshot(arg); 22 | snapshotVerifier.validateSnapshots(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/ReflectionUtilities.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 4 | import java.lang.reflect.Method; 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | public class ReflectionUtilities { 9 | 10 | // FIXME consider guava reflection instead 11 | public static Method getMethod(Class clazz, String methodName) { 12 | try { 13 | return Stream.of(clazz.getDeclaredMethods()) 14 | .filter(method -> method.getName().equals(methodName)) 15 | .findFirst() 16 | .orElseThrow(() -> new NoSuchMethodException("Not Found")); 17 | } catch (NoSuchMethodException e) { 18 | return Optional.ofNullable(clazz.getSuperclass()) 19 | .map(superclass -> getMethod(superclass, methodName)) 20 | .orElseThrow( 21 | () -> 22 | new SnapshotMatchException( 23 | "Could not find method " 24 | + methodName 25 | + " on class " 26 | + clazz 27 | + "\nPlease annotate your test method with @Test and make it without any parameters!")); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/ScenarioTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import au.com.origin.snapshots.config.SnapshotConfig; 7 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | 11 | class ScenarioTest { 12 | 13 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 14 | 15 | @Test 16 | void canTakeMultipleSnapshotsUsingScenario(TestInfo testInfo) { 17 | SnapshotVerifier snapshotVerifier = 18 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 19 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 20 | expect.toMatchSnapshot("Default Snapshot"); 21 | expect.scenario("additional").toMatchSnapshot("Additional Snapshot"); 22 | snapshotVerifier.validateSnapshots(); 23 | } 24 | 25 | @Test 26 | void canTakeTheSameSnapshotTwice(TestInfo testInfo) { 27 | SnapshotVerifier snapshotVerifier = 28 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 29 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 30 | expect.toMatchSnapshot("Default Snapshot"); 31 | expect.toMatchSnapshot("Default Snapshot"); 32 | expect.scenario("scenario").toMatchSnapshot("Scenario Snapshot"); 33 | expect.scenario("scenario").toMatchSnapshot("Scenario Snapshot"); 34 | snapshotVerifier.validateSnapshots(); 35 | } 36 | 37 | @Test 38 | void cannotTakeDifferentSnapshotsAtDefaultLevel(TestInfo testInfo) { 39 | SnapshotVerifier snapshotVerifier = 40 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 41 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 42 | expect.toMatchSnapshot("Default Snapshot"); 43 | assertThrows(SnapshotMatchException.class, () -> expect.toMatchSnapshot("Default Snapshot 2")); 44 | } 45 | 46 | @Test 47 | void cannotTakeDifferentSnapshotsAtScenarioLevel(TestInfo testInfo) { 48 | SnapshotVerifier snapshotVerifier = 49 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 50 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 51 | expect.scenario("scenario").toMatchSnapshot("Default Snapshot"); 52 | assertThrows( 53 | SnapshotMatchException.class, 54 | () -> expect.scenario("scenario").toMatchSnapshot("Default Snapshot 2")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotCaptor.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.exceptions.SnapshotExtensionException; 4 | import java.lang.reflect.Constructor; 5 | import java.lang.reflect.Field; 6 | 7 | class SnapshotCaptor { 8 | 9 | private Class parameterClass; 10 | 11 | private Class argumentClass; 12 | 13 | private String[] ignore; 14 | 15 | public SnapshotCaptor(Class parameterClass, String... ignore) { 16 | this.parameterClass = parameterClass; 17 | this.argumentClass = parameterClass; 18 | this.ignore = ignore; 19 | } 20 | 21 | public SnapshotCaptor(Class parameterClass, Class argumentClass, String... ignore) { 22 | this.parameterClass = parameterClass; 23 | this.argumentClass = argumentClass; 24 | this.ignore = ignore; 25 | } 26 | 27 | public Class getParameterClass() { 28 | return parameterClass; 29 | } 30 | 31 | public Object removeIgnored(Object value) { 32 | Object newValue = value; 33 | if (ignore != null && ignore.length > 0) { 34 | newValue = shallowCopy(value); 35 | for (String each : ignore) { 36 | try { 37 | Field field = this.argumentClass.getDeclaredField(each); 38 | field.setAccessible(true); 39 | if (field.getType().isPrimitive()) { 40 | field.setByte(newValue, Integer.valueOf(0).byteValue()); 41 | } else { 42 | field.set(newValue, null); 43 | } 44 | } catch (IllegalAccessException | NoSuchFieldException e) { 45 | throw new SnapshotExtensionException("Invalid Ignore value " + each, e.getCause()); 46 | } 47 | } 48 | } 49 | return newValue; 50 | } 51 | 52 | private Object shallowCopy(Object value) { 53 | try { 54 | Object newValue = constructCopy(this.argumentClass); 55 | 56 | Field[] fields = this.argumentClass.getDeclaredFields(); 57 | 58 | for (Field field : fields) { 59 | field.setAccessible(true); 60 | try { 61 | field.set(newValue, field.get(value)); 62 | } catch (Exception e) { 63 | // ignore 64 | } 65 | } 66 | return newValue; 67 | } catch (Exception e) { 68 | throw new SnapshotExtensionException( 69 | "Class " 70 | + this.argumentClass.getSimpleName() 71 | + " must have a default empty constructor!"); 72 | } 73 | } 74 | 75 | private Object constructCopy(Class argumentClass) 76 | throws InstantiationException, IllegalAccessException, 77 | java.lang.reflect.InvocationTargetException, NoSuchMethodException { 78 | 79 | try { 80 | return argumentClass.getDeclaredConstructor().newInstance(); 81 | } catch (Exception e) { 82 | // Ignore - should log 83 | } 84 | 85 | Constructor[] constructors = argumentClass.getDeclaredConstructors(); 86 | 87 | if (constructors.length == 0) { 88 | return argumentClass.getDeclaredConstructor().newInstance(); 89 | } 90 | 91 | int i = 0; 92 | Class[] types = constructors[i].getParameterTypes(); 93 | Object[] paramValues = new Object[types.length]; 94 | 95 | for (int j = 0; j < types.length; j++) { 96 | if (types[j].isPrimitive()) { 97 | paramValues[j] = Integer.valueOf(0).byteValue(); 98 | } else { 99 | paramValues[j] = constructCopy(types[j]); 100 | } 101 | } 102 | return constructors[i].newInstance(paramValues); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotHeaders.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 4 | import au.com.origin.snapshots.config.SnapshotConfig; 5 | import au.com.origin.snapshots.serializers.LowercaseToStringSerializer; 6 | import au.com.origin.snapshots.serializers.SerializerType; 7 | import lombok.NoArgsConstructor; 8 | import org.junit.jupiter.api.AfterAll; 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.TestInfo; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.mockito.junit.jupiter.MockitoExtension; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | public class SnapshotHeaders { 17 | 18 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 19 | 20 | static SnapshotVerifier snapshotVerifier; 21 | 22 | @BeforeAll 23 | static void beforeAll() { 24 | SnapshotUtils.copyTestSnapshots(); 25 | snapshotVerifier = new SnapshotVerifier(DEFAULT_CONFIG, SnapshotHeaders.class); 26 | } 27 | 28 | @AfterAll 29 | static void afterAll() { 30 | snapshotVerifier.validateSnapshots(); 31 | } 32 | 33 | @Test 34 | void shouldBeAbleToSnapshotASingleCustomHeader(TestInfo testInfo) { 35 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 36 | expect.serializer(CustomHeadersSerializer.class).toMatchSnapshot("Hello World"); 37 | } 38 | 39 | @Test 40 | void shouldBeAbleToSnapshotMultipleCustomHeader(TestInfo testInfo) { 41 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 42 | expect.serializer(CustomHeadersSerializer.class).toMatchSnapshot("Hello World"); 43 | } 44 | 45 | @NoArgsConstructor 46 | private static class CustomHeadersSerializer extends LowercaseToStringSerializer { 47 | @Override 48 | public String getOutputFormat() { 49 | return SerializerType.JSON.name(); 50 | } 51 | 52 | @Override 53 | public Snapshot apply(Object object, SnapshotSerializerContext snapshotSerializerContext) { 54 | snapshotSerializerContext.getHeader().put("custom", "anything"); 55 | snapshotSerializerContext.getHeader().put("custom2", "anything2"); 56 | return super.apply(object, snapshotSerializerContext); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import au.com.origin.snapshots.config.SnapshotConfig; 7 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 8 | import au.com.origin.snapshots.serializers.UppercaseToStringSerializer; 9 | import org.junit.jupiter.api.AfterAll; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.TestInfo; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.mockito.junit.jupiter.MockitoExtension; 15 | 16 | @ExtendWith(MockitoExtension.class) 17 | public class SnapshotIntegrationTest { 18 | 19 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 20 | 21 | static SnapshotVerifier snapshotVerifier; 22 | 23 | @BeforeAll 24 | static void beforeAll() { 25 | SnapshotUtils.copyTestSnapshots(); 26 | snapshotVerifier = new SnapshotVerifier(DEFAULT_CONFIG, SnapshotIntegrationTest.class); 27 | } 28 | 29 | @AfterAll 30 | static void afterAll() { 31 | snapshotVerifier.validateSnapshots(); 32 | } 33 | 34 | @Test 35 | void shouldMatchSnapshotOne(TestInfo testInfo) { 36 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 37 | expect.toMatchSnapshot(FakeObject.builder().id("anyId1").value(1).name("anyName1").build()); 38 | } 39 | 40 | @Test 41 | void shouldMatchSnapshotTwo(TestInfo testInfo) { 42 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 43 | expect.toMatchSnapshot(FakeObject.builder().id("anyId2").value(2).name("anyName2").build()); 44 | } 45 | 46 | @Test 47 | void shouldMatchSnapshotThree(TestInfo testInfo) { 48 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 49 | expect.toMatchSnapshot(FakeObject.builder().id("anyId3").value(3).name("anyName3").build()); 50 | } 51 | 52 | @Test 53 | void shouldMatchSnapshotFour(TestInfo testInfo) { 54 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 55 | expect.toMatchSnapshot( 56 | FakeObject.builder().id("anyId4").value(4).name("any\n\n\nName4").build()); 57 | } 58 | 59 | @Test 60 | void shouldMatchSnapshotInsidePrivateMethod(TestInfo testInfo) { 61 | matchInsidePrivate(testInfo); 62 | } 63 | 64 | @Test 65 | void shouldThrowSnapshotMatchException(TestInfo testInfo) { 66 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 67 | assertThrows( 68 | SnapshotMatchException.class, 69 | () -> 70 | expect.toMatchSnapshot( 71 | FakeObject.builder().id("anyId5").value(6).name("anyName5").build()), 72 | "Error on: \n" 73 | + "au.com.origin.snapshots.SnapshotIntegrationTest.shouldThrowSnapshotMatchException=["); 74 | } 75 | 76 | @Test 77 | void shouldSnapshotUsingSerializerClass(TestInfo testInfo) { 78 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 79 | expect.serializer(UppercaseToStringSerializer.class).toMatchSnapshot("Hello World"); 80 | } 81 | 82 | @Test 83 | void shouldSnapshotUsingSerializerPropertyName(TestInfo testInfo) { 84 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 85 | expect.serializer("lowercase").toMatchSnapshot("Hello World"); 86 | } 87 | 88 | private void matchInsidePrivate(TestInfo testInfo) { 89 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 90 | expect.toMatchSnapshot( 91 | FakeObject.builder().id("anyPrivate").value(5).name("anyPrivate").build()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotMatcherScenarioTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | import org.junit.jupiter.api.AfterAll; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.TestInfo; 14 | import org.junit.jupiter.api.extension.ExtendWith; 15 | import org.mockito.junit.jupiter.MockitoExtension; 16 | 17 | @ExtendWith(MockitoExtension.class) 18 | class SnapshotMatcherScenarioTest { 19 | 20 | private static final String FILE_PATH = 21 | "src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotMatcherScenarioTest.snap"; 22 | 23 | static SnapshotVerifier snapshotVerifier; 24 | 25 | @BeforeAll 26 | static void beforeAll() { 27 | snapshotVerifier = 28 | new SnapshotVerifier(new BaseSnapshotConfig(), SnapshotMatcherScenarioTest.class); 29 | } 30 | 31 | @AfterAll 32 | static void afterAll() throws IOException { 33 | snapshotVerifier.validateSnapshots(); 34 | File f = new File(FILE_PATH); 35 | assertThat(String.join("\n", Files.readAllLines(f.toPath()))) 36 | .isEqualTo( 37 | "au.com.origin.snapshots.SnapshotMatcherScenarioTest.should1ShowSnapshotSuccessfully[Scenario A]=[\n" 38 | + "any type of object\n" 39 | + "]\n\n\n" 40 | + "au.com.origin.snapshots.SnapshotMatcherScenarioTest.should2SecondSnapshotExecutionSuccessfully[Scenario B]=[\n" 41 | + "any second type of object\n" 42 | + "]"); 43 | Files.delete(Paths.get(FILE_PATH)); 44 | } 45 | 46 | @Test 47 | void should1ShowSnapshotSuccessfully(TestInfo testInfo) { 48 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 49 | expect.scenario("Scenario A").toMatchSnapshot("any type of object"); 50 | File f = new File(FILE_PATH); 51 | if (!f.exists() || f.isDirectory()) { 52 | throw new RuntimeException("File should exist here"); 53 | } 54 | } 55 | 56 | @Test 57 | void should2SecondSnapshotExecutionSuccessfully(TestInfo testInfo) { 58 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 59 | expect.scenario("Scenario B").toMatchSnapshot("any second type of object"); 60 | File f = new File(FILE_PATH); 61 | if (!f.exists() || f.isDirectory()) { 62 | throw new RuntimeException("File should exist here"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotMatcherTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | import org.junit.jupiter.api.AfterAll; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.TestInfo; 14 | import org.junit.jupiter.api.extension.ExtendWith; 15 | import org.mockito.junit.jupiter.MockitoExtension; 16 | 17 | @ExtendWith(MockitoExtension.class) 18 | class SnapshotMatcherTest { 19 | 20 | private static final String FILE_PATH = 21 | "src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotMatcherTest.snap"; 22 | 23 | static SnapshotVerifier snapshotVerifier; 24 | 25 | @BeforeAll 26 | static void beforeAll() { 27 | snapshotVerifier = new SnapshotVerifier(new BaseSnapshotConfig(), SnapshotMatcherTest.class); 28 | } 29 | 30 | @AfterAll 31 | static void afterAll() throws IOException { 32 | snapshotVerifier.validateSnapshots(); 33 | File f = new File(FILE_PATH); 34 | assertThat(String.join("\n", Files.readAllLines(f.toPath()))) 35 | .isEqualTo( 36 | "au.com.origin.snapshots.SnapshotMatcherTest.should1ShowSnapshotSuccessfully=[\n" 37 | + "any type of object\n" 38 | + "]\n\n\n" 39 | + "au.com.origin.snapshots.SnapshotMatcherTest.should2SecondSnapshotExecutionSuccessfully=[\n" 40 | + "any second type of object\n" 41 | + "]"); 42 | Files.delete(Paths.get(FILE_PATH)); 43 | } 44 | 45 | @Test 46 | void should1ShowSnapshotSuccessfully(TestInfo testInfo) { 47 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 48 | expect.toMatchSnapshot("any type of object"); 49 | File f = new File(FILE_PATH); 50 | if (!f.exists() || f.isDirectory()) { 51 | throw new RuntimeException("File should exist here"); 52 | } 53 | } 54 | 55 | @Test 56 | void should2SecondSnapshotExecutionSuccessfully(TestInfo testInfo) { 57 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 58 | expect.toMatchSnapshot("any second type of object"); 59 | File f = new File(FILE_PATH); 60 | if (!f.exists() || f.isDirectory()) { 61 | throw new RuntimeException("File should exist here"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotNameAnnotationWithDuplicatesTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import au.com.origin.snapshots.annotations.SnapshotName; 6 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 7 | import au.com.origin.snapshots.exceptions.SnapshotExtensionException; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | 11 | public class SnapshotNameAnnotationWithDuplicatesTest { 12 | 13 | @SnapshotName("hello_world") 14 | @Test 15 | void canUseSnapshotNameAnnotation(TestInfo testInfo) { 16 | assertThrows( 17 | SnapshotExtensionException.class, 18 | () -> new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()), 19 | "Oops, looks like you set the same name of two separate snapshots @SnapshotName(\"hello_world\") in class au.com.origin.snapshots.SnapshotNameAnnotationTest"); 20 | } 21 | 22 | @SnapshotName("hello_world") 23 | private void anotherMethodWithSameSnapshotName() {} 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotOverrideClassTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | 7 | public class SnapshotOverrideClassTest extends SnapshotSuperClassTest { 8 | 9 | @BeforeEach 10 | void beforeEach() { 11 | snapshotVerifier = 12 | new SnapshotVerifier(new BaseSnapshotConfig(), SnapshotOverrideClassTest.class); 13 | } 14 | 15 | @AfterEach 16 | void afterEach() { 17 | snapshotVerifier.validateSnapshots(); 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return "anyName"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotSuperClassTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestInfo; 7 | 8 | public abstract class SnapshotSuperClassTest { 9 | 10 | @Getter @Setter static SnapshotVerifier snapshotVerifier; 11 | 12 | public abstract String getName(); 13 | 14 | @Test 15 | void shouldMatchSnapshotOne(TestInfo testInfo) { 16 | Expect.of(snapshotVerifier, testInfo.getTestMethod().get()).toMatchSnapshot(getName()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotUtils.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.mockito.Mockito.atLeastOnce; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 7 | import java.io.IOException; 8 | import java.lang.reflect.Parameter; 9 | import java.nio.file.Paths; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import org.apache.commons.io.FileUtils; 15 | import org.mockito.ArgumentCaptor; 16 | 17 | public class SnapshotUtils { 18 | 19 | public static HashMap>> extractArgs( 20 | T object, String methodName, SnapshotCaptor... snapshotCaptors) { 21 | List captors = new ArrayList<>(); 22 | Class[] classes = new Class[snapshotCaptors.length]; 23 | 24 | int i = 0; 25 | for (SnapshotCaptor snapshotCaptor : snapshotCaptors) { 26 | classes[i] = snapshotCaptor.getParameterClass(); 27 | captors.add(ArgumentCaptor.forClass(snapshotCaptor.getParameterClass())); 28 | i++; 29 | } 30 | 31 | return process(object, methodName, captors, classes, snapshotCaptors); 32 | } 33 | 34 | public static HashMap>> extractArgs( 35 | T object, String methodName, Class... classes) { 36 | List captors = new ArrayList<>(); 37 | 38 | for (Class clazz : classes) { 39 | captors.add(ArgumentCaptor.forClass(clazz)); 40 | } 41 | 42 | return process(object, methodName, captors, classes, null); 43 | } 44 | 45 | private static HashMap>> process( 46 | T object, 47 | String methodName, 48 | List captors, 49 | Class[] classes, 50 | SnapshotCaptor[] snapshotCaptors) { 51 | HashMap>> result = new HashMap<>(); 52 | try { 53 | Parameter[] parameters = 54 | object.getClass().getDeclaredMethod(methodName, classes).getParameters(); 55 | 56 | object 57 | .getClass() 58 | .getDeclaredMethod(methodName, classes) 59 | .invoke( 60 | verify(object, atLeastOnce()), 61 | captors.stream().map(ArgumentCaptor::capture).toArray()); 62 | 63 | List> extractedObjects = new ArrayList<>(); 64 | 65 | int numberOfCall; 66 | 67 | if (captors.size() > 0) { 68 | numberOfCall = captors.get(0).getAllValues().size(); 69 | 70 | for (int i = 0; i < numberOfCall; i++) { 71 | LinkedHashMap objectMap = new LinkedHashMap<>(); 72 | 73 | int j = 0; 74 | for (ArgumentCaptor captor : captors) { 75 | Object value = captor.getAllValues().get(i); 76 | if (snapshotCaptors != null) { 77 | value = snapshotCaptors[j].removeIgnored(value); 78 | } 79 | objectMap.put(parameters[j].getName(), value); 80 | j++; 81 | } 82 | extractedObjects.add(objectMap); 83 | } 84 | } 85 | 86 | result.put( 87 | object.getClass().getSuperclass().getSimpleName() + "." + methodName, extractedObjects); 88 | } catch (Exception e) { 89 | throw new SnapshotMatchException(e.getMessage(), e.getCause()); 90 | } 91 | 92 | return result; 93 | } 94 | 95 | public static void copyTestSnapshots() { 96 | try { 97 | FileUtils.copyDirectory( 98 | Paths.get("src/test/java/au/com/origin/snapshots/existing-snapshots").toFile(), 99 | Paths.get("src/test/java/au/com/origin/snapshots").toFile()); 100 | } catch (IOException e) { 101 | e.printStackTrace(); 102 | throw new RuntimeException("Can't move files to __snapshots__ folder"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotUtilsTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static au.com.origin.snapshots.SnapshotUtils.extractArgs; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.Mock; 12 | import org.mockito.junit.jupiter.MockitoExtension; 13 | 14 | @ExtendWith(MockitoExtension.class) 15 | class SnapshotUtilsTest { 16 | 17 | @Mock private FakeObject fakeObject; 18 | 19 | @Test 20 | void shouldExtractArgsFromFakeMethod(TestInfo testInfo) { 21 | fakeObject.fakeMethod("test1", 1L, Arrays.asList("listTest1")); 22 | fakeObject.fakeMethod("test2", 2L, Arrays.asList("listTest1", "listTest2")); 23 | 24 | SnapshotVerifier snapshotVerifier = 25 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()); 26 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 27 | expect.toMatchSnapshot( 28 | extractArgs( 29 | fakeObject, 30 | "fakeMethod", 31 | new SnapshotCaptor(String.class), 32 | new SnapshotCaptor(Long.class), 33 | new SnapshotCaptor(List.class))); 34 | snapshotVerifier.validateSnapshots(); 35 | } 36 | 37 | @Test 38 | void shouldExtractArgsFromFakeMethodWithComplexObject(TestInfo testInfo) { 39 | FakeObject fake = new FakeObject.FakeObjectBuilder().id("idMock").name("nameMock").build(); 40 | 41 | // With Ignore 42 | fakeObject.fakeMethodWithComplexFakeObject(fake); 43 | Object fakeMethodWithComplexObjectWithIgnore = 44 | extractArgs( 45 | fakeObject, 46 | "fakeMethodWithComplexFakeObject", 47 | new SnapshotCaptor(FakeObject.class, "name")); 48 | 49 | SnapshotVerifier snapshotVerifier = 50 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()); 51 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 52 | expect.toMatchSnapshot(fakeMethodWithComplexObjectWithIgnore); 53 | snapshotVerifier.validateSnapshots(); 54 | } 55 | 56 | @Test 57 | void shouldExtractArgsFromFakeMethodWithComplexFakeObject(TestInfo testInfo) { 58 | 59 | FakeObject fake = new FakeObject.FakeObjectBuilder().id("idMock").name("nameMock").build(); 60 | 61 | // With Ignore 62 | fakeObject.fakeMethodWithComplexObject(fake); 63 | Object fakeMethodWithComplexObjectWithIgnore = 64 | extractArgs( 65 | fakeObject, 66 | "fakeMethodWithComplexObject", 67 | new SnapshotCaptor(Object.class, FakeObject.class, "name")); 68 | 69 | SnapshotVerifier snapshotVerifier = 70 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get()); 71 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 72 | expect.toMatchSnapshot(fakeMethodWithComplexObjectWithIgnore); 73 | snapshotVerifier.validateSnapshots(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/UpdateSnapshotPropertyTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 6 | import au.com.origin.snapshots.config.SnapshotConfig; 7 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import org.assertj.core.api.Assertions; 15 | import org.junit.jupiter.api.*; 16 | import org.junit.jupiter.api.extension.ExtendWith; 17 | import org.mockito.junit.jupiter.MockitoExtension; 18 | 19 | @Deprecated 20 | @ExtendWith(MockitoExtension.class) 21 | public class UpdateSnapshotPropertyTest { 22 | 23 | @AfterAll 24 | static void afterAll() { 25 | System.clearProperty(SnapshotConfig.JVM_UPDATE_SNAPSHOTS_PARAMETER); 26 | } 27 | 28 | @BeforeEach 29 | public void beforeEach() throws Exception { 30 | File file = 31 | new File( 32 | "src/test/java/au/com/origin/snapshots/__snapshots__/UpdateSnapshotPropertyTest.snap"); 33 | String content = 34 | "au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldNotUpdateSnapshot=[\n" 35 | + "FakeObject(id=ERROR, value=1, name=anyName1, fakeObject=null)\n" 36 | + "]\n" 37 | + "\n" 38 | + "\n" 39 | + "au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldUpdateSnapshot=[\n" 40 | + "FakeObject(id=ERROR, value=2, name=anyName2, fakeObject=null)\n" 41 | + "]"; 42 | Path parentDir = file.getParentFile().toPath(); 43 | if (!Files.exists(parentDir)) { 44 | Files.createDirectories(parentDir); 45 | } 46 | Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8)); 47 | } 48 | 49 | @Test 50 | void shouldUpdateSnapshot(TestInfo testInfo) throws IOException { 51 | SnapshotVerifier snapshotVerifier = 52 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get(), false); 53 | System.setProperty(SnapshotConfig.JVM_UPDATE_SNAPSHOTS_PARAMETER, ""); 54 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 55 | expect.toMatchSnapshot(FakeObject.builder().id("anyId2").value(2).name("anyName2").build()); 56 | snapshotVerifier.validateSnapshots(); 57 | 58 | String content = 59 | new String( 60 | Files.readAllBytes( 61 | Paths.get( 62 | "src/test/java/au/com/origin/snapshots/__snapshots__/UpdateSnapshotPropertyTest.snap")), 63 | StandardCharsets.UTF_8); 64 | Assertions.assertThat(content) 65 | .isEqualTo( 66 | "au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldNotUpdateSnapshot=[\n" 67 | + "FakeObject(id=ERROR, value=1, name=anyName1, fakeObject=null)\n" 68 | + "]\n" 69 | + "\n" 70 | + "\n" 71 | + "au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldUpdateSnapshot=[\n" 72 | + "FakeObject(id=anyId2, value=2, name=anyName2, fakeObject=null)\n" 73 | + "]"); 74 | } 75 | 76 | @Test 77 | void shouldNotUpdateSnapshot(TestInfo testInfo) { 78 | SnapshotVerifier snapshotVerifier = 79 | new SnapshotVerifier(new BaseSnapshotConfig(), testInfo.getTestClass().get(), false); 80 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 81 | System.setProperty(SnapshotConfig.JVM_UPDATE_SNAPSHOTS_PARAMETER, "true"); 82 | assertThrows( 83 | SnapshotMatchException.class, 84 | () -> 85 | expect.toMatchSnapshot( 86 | FakeObject.builder().id("anyId1").value(1).name("anyName1").build()), 87 | "Error on: \n" 88 | + "au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldNotUpdateSnapshot=["); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/UseCustomConfigTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.annotations.UseSnapshotConfig; 4 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 5 | import au.com.origin.snapshots.config.SnapshotConfig; 6 | import au.com.origin.snapshots.config.ToStringSnapshotConfig; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | @UseSnapshotConfig(ToStringSnapshotConfig.class) 14 | @ExtendWith(MockitoExtension.class) 15 | public class UseCustomConfigTest { 16 | 17 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 18 | 19 | @BeforeAll 20 | static void beforeAll() { 21 | SnapshotUtils.copyTestSnapshots(); 22 | } 23 | 24 | @Test 25 | void canUseSnapshotConfigAnnotationAtClassLevel(TestInfo testInfo) { 26 | SnapshotVerifier snapshotVerifier = 27 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 28 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 29 | expect.toMatchSnapshot(new TestObject()); 30 | snapshotVerifier.validateSnapshots(); 31 | } 32 | 33 | private class TestObject { 34 | @Override 35 | public String toString() { 36 | return "This is a snapshot of the toString() method"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/UseCustomSerializerTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.config.BaseSnapshotConfig; 4 | import au.com.origin.snapshots.config.SnapshotConfig; 5 | import au.com.origin.snapshots.serializers.UppercaseToStringSerializer; 6 | import org.junit.jupiter.api.BeforeAll; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | @ExtendWith(MockitoExtension.class) 14 | public class UseCustomSerializerTest { 15 | 16 | private static final SnapshotConfig DEFAULT_CONFIG = new BaseSnapshotConfig(); 17 | 18 | @BeforeAll 19 | static void beforeEach() { 20 | SnapshotUtils.copyTestSnapshots(); 21 | } 22 | 23 | @DisplayName("@SnapshotSerializer on a method via new instance") 24 | @Test 25 | public void canUseSnapshotSerializerAnnotationAtMethodLevelUsingNewInstance(TestInfo testInfo) { 26 | SnapshotVerifier snapshotVerifier = 27 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 28 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 29 | expect.serializer(new UppercaseToStringSerializer()).toMatchSnapshot(new TestObject()); 30 | snapshotVerifier.validateSnapshots(); 31 | } 32 | 33 | @DisplayName("@SnapshotSerializer on a method via class name") 34 | @Test 35 | public void canUseSnapshotSerializerAnnotationAtMethodLevelUsingClassName(TestInfo testInfo) { 36 | SnapshotVerifier snapshotVerifier = 37 | new SnapshotVerifier(DEFAULT_CONFIG, testInfo.getTestClass().get()); 38 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 39 | expect.serializer(new UppercaseToStringSerializer()).toMatchSnapshot(new TestObject()); 40 | snapshotVerifier.validateSnapshots(); 41 | } 42 | 43 | private class TestObject { 44 | @Override 45 | public String toString() { 46 | return "This is a snapshot of the toString() method"; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/comparators/PlainTextEqualsComparatorTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.comparators; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import org.assertj.core.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class PlainTextEqualsComparatorTest { 8 | 9 | private static final PlainTextEqualsComparator COMPARATOR = new PlainTextEqualsComparator(); 10 | 11 | @Test 12 | void successfulComparison() { 13 | Snapshot snap1 = Snapshot.builder().name("snap1").scenario("A").body("foo").build(); 14 | Snapshot snap2 = Snapshot.builder().name("snap1").scenario("A").body("foo").build(); 15 | Assertions.assertThat(COMPARATOR.matches(snap1, snap2)).isTrue(); 16 | } 17 | 18 | @Test 19 | void failingComparison() { 20 | Snapshot snap1 = Snapshot.builder().name("snap1").scenario("A").body("foo").build(); 21 | Snapshot snap2 = Snapshot.builder().name("snap1").scenario("A").body("bar").build(); 22 | Assertions.assertThat(COMPARATOR.matches(snap1, snap2)).isFalse(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/config/BaseSnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.config; 2 | 3 | import au.com.origin.snapshots.comparators.PlainTextEqualsComparator; 4 | import au.com.origin.snapshots.comparators.SnapshotComparator; 5 | import au.com.origin.snapshots.reporters.PlainTextSnapshotReporter; 6 | import au.com.origin.snapshots.reporters.SnapshotReporter; 7 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 8 | import au.com.origin.snapshots.serializers.ToStringSnapshotSerializer; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class BaseSnapshotConfig implements SnapshotConfig { 13 | 14 | @Override 15 | public String getOutputDir() { 16 | return "src/test/java"; 17 | } 18 | 19 | @Override 20 | public String getSnapshotDir() { 21 | return "__snapshots__"; 22 | } 23 | 24 | @Override 25 | public SnapshotSerializer getSerializer() { 26 | return new ToStringSnapshotSerializer(); 27 | } 28 | 29 | @Override 30 | public SnapshotComparator getComparator() { 31 | return new PlainTextEqualsComparator(); 32 | } 33 | 34 | @Override 35 | public List getReporters() { 36 | return Collections.singletonList(new PlainTextSnapshotReporter()); 37 | } 38 | 39 | @Override 40 | public boolean isCI() { 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/config/ToStringSnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.config; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import au.com.origin.snapshots.serializers.SerializerType; 6 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 7 | 8 | public class ToStringSnapshotConfig extends BaseSnapshotConfig { 9 | 10 | @Override 11 | public SnapshotSerializer getSerializer() { 12 | return new SnapshotSerializer() { 13 | @Override 14 | public String getOutputFormat() { 15 | return SerializerType.TEXT.name(); 16 | } 17 | 18 | @Override 19 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 20 | return gen.toSnapshot(object.toString()); 21 | } 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/DebugSnapshotLineEndingsTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.DebugSnapshotLineEndingsTest.existingSnapshotDifferentLineEndings=[ 2 | a 3 | b 4 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/DebugSnapshotTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.DebugSnapshotTest.createDebugFile=[ 2 | Good Snapshot 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedSnapshotMatch=[ 7 | Good Snapshot 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedExistingSnapshot=[ 12 | Good Snapshot 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap.debug: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest$NestedClass.snap.debug -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest.snap -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest.snap.debug: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EmptySnapshotFileTest.snap.debug -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EqualDebugSnapshotFileTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.DebugSnapshotTest.createDebugFile=[ 2 | Good Snapshot 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedSnapshotMatch=[ 7 | Good Snapshot 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedExistingSnapshot=[ 12 | Good Snapshot 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/EqualDebugSnapshotFileTest.snap.debug: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.DebugSnapshotTest.createDebugFile=[ 2 | Good Snapshot 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedSnapshotMatch=[ 7 | Good Snapshot 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.DebugSnapshotTest.debugFileCreatedExistingSnapshot=[ 12 | Good Snapshot 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/OrphanSnapshotTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.OrphanSnapshotTest.orphanMethod=[ 2 | { 3 | "message": "This orphan method should fail the test" 4 | } 5 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/PrivateCalledMethodTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.PrivateCalledMethodTest.testName=[ 2 | testContent 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/README.md: -------------------------------------------------------------------------------- 1 | # Existing snapshots 2 | 3 | These the original and unmodified snapshots, they get copied into __snapshots__ before each test. -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/ScenarioTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.ScenarioTest.canTakeMultipleSnapshotsUsingScenario=[ 2 | Default Snapshot 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.ScenarioTest.canTakeMultipleSnapshotsUsingScenario[additional]=[ 7 | Additional Snapshot 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.ScenarioTest.canTakeTheSameSnapshotTwice=[ 12 | Default Snapshot 13 | ] 14 | 15 | 16 | au.com.origin.snapshots.ScenarioTest.canTakeTheSameSnapshotTwice[scenario]=[ 17 | Scenario Snapshot 18 | ] 19 | 20 | 21 | au.com.origin.snapshots.ScenarioTest.cannotTakeDifferentSnapshotsAtDefaultLevel=[ 22 | Default Snapshot 23 | ] 24 | 25 | 26 | au.com.origin.snapshots.ScenarioTest.cannotTakeDifferentSnapshotsAtScenarioLevel[scenario]=[ 27 | Default Snapshot 28 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/SnapshotHeaders.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotHeaders.shouldBeAbleToSnapshotASingleCustomHeader={ 2 | "custom": "anything", 3 | "custom2": "anything2" 4 | }hello world 5 | 6 | 7 | au.com.origin.snapshots.SnapshotHeaders.shouldBeAbleToSnapshotMultipleCustomHeader={ 8 | "custom": "anything", 9 | "custom2": "anything2" 10 | }hello world -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/SnapshotIntegrationTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldMatchSnapshotFour=[ 2 | FakeObject(id=anyId4, value=4, name=any 3 | . 4 | . 5 | Name4, fakeObject=null) 6 | ] 7 | 8 | 9 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldMatchSnapshotInsidePrivateMethod=[ 10 | FakeObject(id=anyPrivate, value=5, name=anyPrivate, fakeObject=null) 11 | ] 12 | 13 | 14 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldMatchSnapshotOne=[ 15 | FakeObject(id=anyId1, value=1, name=anyName1, fakeObject=null) 16 | ] 17 | 18 | 19 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldMatchSnapshotThree=[ 20 | FakeObject(id=anyId3, value=3, name=anyName3, fakeObject=null) 21 | ] 22 | 23 | 24 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldMatchSnapshotTwo=[ 25 | FakeObject(id=anyId2, value=2, name=anyName2, fakeObject=null) 26 | ] 27 | 28 | 29 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldSnapshotUsingSerializerClass=HELLO WORLD 30 | 31 | 32 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldSnapshotUsingSerializerPropertyName=hello world 33 | 34 | 35 | au.com.origin.snapshots.SnapshotIntegrationTest.shouldThrowSnapshotMatchException=[ 36 | FakeObject(id=anyId5, value=7, name=anyName5, fakeObject=null) 37 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/SnapshotNameAnnotationTest.snap: -------------------------------------------------------------------------------- 1 | can use snapshot name with spaces=[ 2 | Hello World 3 | ] 4 | 5 | 6 | can_use_snapshot_name=[ 7 | Hello World 8 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/SnapshotOverrideClassTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotOverrideClassTest.shouldMatchSnapshotOne=[ 2 | anyName 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/SnapshotUtilsTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotUtilsTest.shouldExtractArgsFromFakeMethod=[ 2 | {FakeObject.fakeMethod=[{arg0=test1, arg1=1, arg2=[listTest1]}, {arg0=test2, arg1=2, arg2=[listTest1, listTest2]}]} 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotUtilsTest.shouldExtractArgsFromFakeMethodWithComplexFakeObject=[ 7 | {FakeObject.fakeMethodWithComplexObject=[{arg0=FakeObject(id=idMock, value=null, name=null, fakeObject=null)}]} 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.SnapshotUtilsTest.shouldExtractArgsFromFakeMethodWithComplexObject=[ 12 | {FakeObject.fakeMethodWithComplexFakeObject=[{arg0=FakeObject(id=idMock, value=null, name=null, fakeObject=null)}]} 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/UpdateSnapshotPropertyTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldNotUpdateSnapshot=[ 2 | FakeObject(id=ERROR, value=1, name=anyName1, fakeObject=null) 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.UpdateSnapshotPropertyTest.shouldUpdateSnapshot=[ 7 | FakeObject(id=ERROR, value=1, name=anyName2, fakeObject=null) 8 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/UseCustomConfigTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.UseCustomConfigTest.canUseSnapshotConfigAnnotationAtClassLevel=This is a snapshot of the toString() method 2 | 3 | 4 | au.com.origin.snapshots.UseCustomConfigTest.canUseSnapshotConfigAnnotationAtMethodLevel=THIS IS A SNAPSHOT OF THE TOSTRING() METHOD -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/existing-snapshots/__snapshots__/UseCustomSerializerTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.UseCustomSerializerTest.canUseSnapshotSerializerAnnotationAtClassLevel=this is a snapshot of the tostring() method 2 | 3 | 4 | au.com.origin.snapshots.UseCustomSerializerTest.canUseSnapshotSerializerAnnotationAtMethodLevel=THIS IS A SNAPSHOT OF THE TOSTRING() METHOD 5 | 6 | 7 | au.com.origin.snapshots.UseCustomSerializerTest.canUseSnapshotSerializerAnnotationAtMethodLevelUsingClassName=THIS IS A SNAPSHOT OF THE TOSTRING() METHOD 8 | 9 | 10 | au.com.origin.snapshots.UseCustomSerializerTest.canUseSnapshotSerializerAnnotationAtMethodLevelUsingNewInstance=THIS IS A SNAPSHOT OF THE TOSTRING() METHOD -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/reporters/PlainTextSnapshotReporterTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.reporters; 2 | 3 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 4 | 5 | import au.com.origin.snapshots.Snapshot; 6 | import au.com.origin.snapshots.serializers.SerializerType; 7 | import org.assertj.core.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | import org.opentest4j.AssertionFailedError; 10 | 11 | class PlainTextSnapshotReporterTest { 12 | private static final PlainTextSnapshotReporter REPORTER = new PlainTextSnapshotReporter(); 13 | 14 | @Test 15 | void shouldSupportAllFormats() { 16 | Assertions.assertThat(REPORTER.supportsFormat(SerializerType.TEXT.name())).isTrue(); 17 | Assertions.assertThat(REPORTER.supportsFormat(SerializerType.JSON.name())).isTrue(); 18 | 19 | Assertions.assertThat(REPORTER.supportsFormat("xml")).isTrue(); 20 | Assertions.assertThat(REPORTER.supportsFormat("blah")).isTrue(); 21 | } 22 | 23 | @Test 24 | void doReport() { 25 | Snapshot snap1 = Snapshot.builder().name("snap1").scenario("A").body("[\nfoo\n]").build(); 26 | Snapshot snap2 = Snapshot.builder().name("snap1").scenario("A").body("[\nbar\n]").build(); 27 | assertThatExceptionOfType(AssertionFailedError.class) 28 | .isThrownBy(() -> REPORTER.report(snap1, snap2)) 29 | .withMessageContaining("expecting:") 30 | .withMessageContaining("[\"foo\"]") 31 | .withMessageContaining("but was:") 32 | .withMessageContaining("[\"bar\"]"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/serializers/LowercaseToStringSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | 6 | public class LowercaseToStringSerializer implements SnapshotSerializer { 7 | @Override 8 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 9 | return gen.toSnapshot(object.toString().toLowerCase()); 10 | } 11 | 12 | @Override 13 | public String getOutputFormat() { 14 | return SerializerType.TEXT.name(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/serializers/ToStringSnapshotSerializerTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.Snapshot; 6 | import au.com.origin.snapshots.SnapshotHeader; 7 | import au.com.origin.snapshots.SnapshotSerializerContext; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Data; 10 | import org.junit.jupiter.api.Test; 11 | 12 | public class ToStringSnapshotSerializerTest { 13 | ToStringSnapshotSerializer serializer = new ToStringSnapshotSerializer(); 14 | 15 | private SnapshotSerializerContext mockSnapshotGenerator = 16 | new SnapshotSerializerContext( 17 | "base64Test", 18 | null, 19 | new SnapshotHeader(), 20 | ToStringSnapshotSerializerTest.class, 21 | null // it's not used in these scenarios 22 | ); 23 | 24 | @Test 25 | void shouldSnapshotAnyString() { 26 | Snapshot result = serializer.apply("John Doe", mockSnapshotGenerator); 27 | assertThat(result.getBody()).isEqualTo("[\nJohn Doe\n]"); 28 | } 29 | 30 | @Test 31 | void shouldSnapshotUnicode() { 32 | Snapshot result = serializer.apply("🤔", mockSnapshotGenerator); 33 | assertThat(result.getBody()).isEqualTo("[\n🤔\n]"); 34 | } 35 | 36 | @Test 37 | void shouldSnapshotAnyObject() { 38 | Snapshot result = serializer.apply(new Dummy(1, "John Doe"), mockSnapshotGenerator); 39 | assertThat(result.getBody()) 40 | .isEqualTo("[\nToStringSerializerTest.Dummy(id=1, name=John Doe)\n]"); 41 | } 42 | 43 | @Test 44 | void shouldSnapshotMultipleObjects() { 45 | Snapshot result = serializer.apply(new Dummy(1, "John Doe"), mockSnapshotGenerator); 46 | assertThat(result.getBody()) 47 | .isEqualTo("[\nToStringSerializerTest.Dummy(id=1, name=John Doe)\n]"); 48 | } 49 | 50 | @Test 51 | void shouldSupportBase64SerializerType() { 52 | assertThat(serializer.getOutputFormat()).isEqualTo("TEXT"); 53 | } 54 | 55 | @Test 56 | void shouldReplaceThreeConsecutiveNewLines() { 57 | Snapshot result = serializer.apply("John\n\n\nDoe", mockSnapshotGenerator); 58 | assertThat(result.getBody()).isEqualTo("[\nJohn\n.\n.\nDoe\n]"); 59 | } 60 | 61 | @Test 62 | void shouldReplaceTwoConsecutiveNewLinesAtEnd() { 63 | Snapshot result = serializer.apply("John Doe\n\n", mockSnapshotGenerator); 64 | assertThat(result.getBody()).isEqualTo("[\nJohn Doe\n.\n.\n]"); 65 | } 66 | 67 | @Test 68 | void shouldReplaceTwoConsecutiveNewLinesAtBeginning() { 69 | Snapshot result = serializer.apply("\n\nJohn Doe", mockSnapshotGenerator); 70 | assertThat(result.getBody()).isEqualTo("[\n.\n.\nJohn Doe\n]"); 71 | } 72 | 73 | @Test 74 | void shouldReplaceIllegalNewlineSequencesEverywhere() { 75 | Snapshot result = serializer.apply("\n\nJohn\n\n\nDoe\n\n", mockSnapshotGenerator); 76 | assertThat(result.getBody()).isEqualTo("[\n.\n.\nJohn\n.\n.\nDoe\n.\n.\n]"); 77 | } 78 | 79 | @AllArgsConstructor 80 | @Data 81 | private static class Dummy { 82 | private int id; 83 | private String name; 84 | 85 | public String toString() { 86 | return "ToStringSerializerTest.Dummy(id=" + this.getId() + ", name=" + this.getName() + ")"; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/serializers/UppercaseToStringSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.serializers; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | 6 | public class UppercaseToStringSerializer implements SnapshotSerializer { 7 | @Override 8 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 9 | return gen.toSnapshot(object.toString().toUpperCase()); 10 | } 11 | 12 | @Override 13 | public String getOutputFormat() { 14 | return SerializerType.TEXT.name(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/resources/origin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/origin-energy/java-snapshot-testing/2e2087bb1d005216c2e14a2bee681e58bc31d38f/java-snapshot-testing-core/src/test/resources/origin-logo.png -------------------------------------------------------------------------------- /java-snapshot-testing-core/src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer.lowercase=au.com.origin.snapshots.serializers.LowercaseToStringSerializer -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../gradle/publishing.gradle" 2 | apply from: "../gradle/spotless.gradle" 3 | 4 | dependencies { 5 | implementation project(':java-snapshot-testing-core') 6 | 7 | // User supplied JUnit4 Version 8 | compileOnly 'org.junit.platform:junit-platform-runner:1.2.0' 9 | compileOnly 'org.junit.vintage:junit-vintage-engine:5.2.0' 10 | 11 | // Testing 12 | testImplementation 'org.slf4j:slf4j-simple:2.0.0-alpha0' 13 | testImplementation 'org.junit.platform:junit-platform-runner:1.2.0' 14 | testImplementation 'org.junit.vintage:junit-vintage-engine:5.2.0' 15 | testImplementation 'org.assertj:assertj-core:3.11.1' 16 | 17 | // Required java-snapshot-testing peer dependencies 18 | testImplementation 'com.fasterxml.jackson.core:jackson-core:2.11.3' 19 | testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.11.3' 20 | } 21 | 22 | publishing { 23 | publications { 24 | myPublication(MavenPublication) { 25 | artifact shadowJar 26 | groupId 'io.github.origin-energy' 27 | artifactId 'java-snapshot-testing-junit4' 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/main/java/au/com/origin/snapshots/junit4/SharedSnapshotHelpers.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.junit4; 2 | 3 | import au.com.origin.snapshots.*; 4 | import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig; 5 | import au.com.origin.snapshots.config.SnapshotConfig; 6 | import au.com.origin.snapshots.config.SnapshotConfigInjector; 7 | import au.com.origin.snapshots.utils.ReflectionUtils; 8 | import java.lang.reflect.Method; 9 | import java.util.Arrays; 10 | import org.junit.runner.Description; 11 | import org.junit.runners.model.FrameworkMethod; 12 | import org.junit.runners.model.Statement; 13 | 14 | class SharedSnapshotHelpers implements SnapshotConfigInjector { 15 | 16 | public void injectExpectInstanceVariable( 17 | SnapshotVerifier snapshotVerifier, Method testMethod, Object testInstance) { 18 | 19 | ReflectionUtils.findFieldByPredicate( 20 | testInstance.getClass(), (field) -> field.getType() == Expect.class) 21 | .ifPresent( 22 | (field) -> { 23 | Expect expect = Expect.of(snapshotVerifier, testMethod); 24 | ReflectionUtils.makeAccessible(field); 25 | try { 26 | field.set(testInstance, expect); 27 | } catch (IllegalAccessException e) { 28 | throw new RuntimeException(e); 29 | } 30 | }); 31 | } 32 | 33 | public Statement injectExpectMethodArgument( 34 | SnapshotVerifier snapshotVerifier, FrameworkMethod method, Object test) { 35 | return new Statement() { 36 | @Override 37 | public void evaluate() throws Throwable { 38 | method.invokeExplosively(test, new Expect(snapshotVerifier, method.getMethod())); 39 | } 40 | }; 41 | } 42 | 43 | public boolean hasExpectArgument(FrameworkMethod method) { 44 | return Arrays.asList(method.getMethod().getParameterTypes()).contains(Expect.class); 45 | } 46 | 47 | @Override 48 | public SnapshotConfig getSnapshotConfig() { 49 | return new PropertyResolvingSnapshotConfig(); 50 | } 51 | 52 | public SnapshotVerifier getSnapshotVerifier(Description description) { 53 | // We don't want the orphan check to happen when the user runs a single test in their IDE 54 | boolean failOnOrphans = description.getChildren().size() > 1; 55 | 56 | return new SnapshotVerifier(getSnapshotConfig(), description.getTestClass(), failOnOrphans); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/main/java/au/com/origin/snapshots/junit4/SnapshotClassRule.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.junit4; 2 | 3 | import au.com.origin.snapshots.SnapshotVerifier; 4 | import lombok.Getter; 5 | import org.junit.rules.TestRule; 6 | import org.junit.runner.Description; 7 | import org.junit.runners.model.Statement; 8 | 9 | public class SnapshotClassRule implements TestRule { 10 | 11 | @Getter private SnapshotVerifier snapshotVerifier; 12 | 13 | @Getter private SharedSnapshotHelpers helpers = new SharedSnapshotHelpers(); 14 | 15 | @Override 16 | public Statement apply(Statement base, Description description) { 17 | return new Statement() { 18 | @Override 19 | public void evaluate() throws Throwable { 20 | snapshotVerifier = helpers.getSnapshotVerifier(description); 21 | try { 22 | base.evaluate(); 23 | } finally { 24 | snapshotVerifier.validateSnapshots(); 25 | } 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/main/java/au/com/origin/snapshots/junit4/SnapshotRule.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.junit4; 2 | 3 | import au.com.origin.snapshots.exceptions.SnapshotExtensionException; 4 | import lombok.RequiredArgsConstructor; 5 | import org.junit.rules.MethodRule; 6 | import org.junit.runners.model.FrameworkMethod; 7 | import org.junit.runners.model.Statement; 8 | 9 | @RequiredArgsConstructor 10 | public class SnapshotRule implements MethodRule { 11 | 12 | private final SnapshotClassRule snapshotClassRule; 13 | 14 | @Override 15 | public Statement apply(Statement base, FrameworkMethod method, Object test) { 16 | final SharedSnapshotHelpers helpers = snapshotClassRule.getHelpers(); 17 | helpers.injectExpectInstanceVariable( 18 | snapshotClassRule.getSnapshotVerifier(), method.getMethod(), test); 19 | if (helpers.hasExpectArgument(method)) { 20 | throw new SnapshotExtensionException( 21 | "Sorry, we don't support 'Expect' as a method argument for @Rule or @ClassRule. " 22 | + "Please use an instance variable or @RunWith(SnapshotRunner.class) instead."); 23 | } 24 | 25 | return new Statement() { 26 | @Override 27 | public void evaluate() throws Throwable { 28 | base.evaluate(); 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/main/java/au/com/origin/snapshots/junit4/SnapshotRunner.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.junit4; 2 | 3 | import au.com.origin.snapshots.SnapshotVerifier; 4 | import au.com.origin.snapshots.logging.LoggingHelper; 5 | import java.util.List; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Test; 8 | import org.junit.runner.notification.RunNotifier; 9 | import org.junit.runners.BlockJUnit4ClassRunner; 10 | import org.junit.runners.model.FrameworkMethod; 11 | import org.junit.runners.model.InitializationError; 12 | import org.junit.runners.model.Statement; 13 | 14 | /** 15 | * Runner to enable java-snapshot-testing 16 | * 17 | *

If you are already using @RunWith for something else such as @RunWith(Parameterized.class) use 18 | * these Rules instead. 19 | * 20 | * @see SnapshotClassRule 21 | * @see SnapshotRule 22 | *

{@code
23 |  * {@literal @}ClassRule
24 |  * public static SnapshotClassRule snapshotClassRule = new SnapshotClassRule();
25 |  *
26 |  * {@literal @}Rule
27 |  * public SnapshotRule snapshotRule = new SnapshotRule(snapshotClassRule);
28 |  *
29 |  * private Expect expect;
30 |  * }
31 | * Loosely based on: 32 | * https://stackoverflow.com/questions/27745691/how-to-combine-runwith-with-runwithparameterized-class 33 | */ 34 | @Slf4j 35 | public class SnapshotRunner extends BlockJUnit4ClassRunner { 36 | 37 | SnapshotVerifier snapshotVerifier; 38 | 39 | private SharedSnapshotHelpers helpers = new SharedSnapshotHelpers(); 40 | 41 | public SnapshotRunner(Class klass) throws InitializationError { 42 | super(klass); 43 | } 44 | 45 | @Override 46 | protected Statement methodInvoker(FrameworkMethod method, Object test) { 47 | boolean isTest = method.getMethod().isAnnotationPresent(Test.class); 48 | if (isTest) { 49 | helpers.injectExpectInstanceVariable(snapshotVerifier, method.getMethod(), test); 50 | boolean shouldInjectMethodArgument = helpers.hasExpectArgument(method); 51 | if (shouldInjectMethodArgument) { 52 | LoggingHelper.deprecatedV5( 53 | log, 54 | "Injecting 'Expect' via method a argument is no longer recommended. Consider using instance variable injection instead."); 55 | return helpers.injectExpectMethodArgument(snapshotVerifier, method, test); 56 | } 57 | } 58 | 59 | return super.methodInvoker(method, test); 60 | } 61 | 62 | @Override 63 | public void run(RunNotifier notifier) { 64 | snapshotVerifier = helpers.getSnapshotVerifier(getDescription()); 65 | super.run(notifier); 66 | snapshotVerifier.validateSnapshots(); 67 | } 68 | 69 | @Override 70 | protected void validateTestMethods(List errors) { 71 | // Disable as it checks for zero arguments 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/BaseClassTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.junit4.SnapshotRunner; 4 | import org.junit.Test; 5 | import org.junit.experimental.runners.Enclosed; 6 | import org.junit.runner.RunWith; 7 | 8 | @RunWith(Enclosed.class) 9 | public class BaseClassTest { 10 | 11 | static class TestBase { 12 | Expect expect; 13 | } 14 | 15 | @RunWith(SnapshotRunner.class) 16 | public static class NestedClass extends TestBase { 17 | 18 | @Test 19 | public void helloWorldTest() { 20 | expect.toMatchSnapshot("Hello World"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/MyTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.junit4.SnapshotRunner; 4 | import junit.framework.TestCase; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | @RunWith(SnapshotRunner.class) 9 | public class MyTest { 10 | 11 | @Test 12 | public void someTest(Expect expect) { 13 | expect.toMatchSnapshot("Hello World"); 14 | } 15 | 16 | @Test 17 | public void aNormalTest() { 18 | TestCase.assertTrue(true); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/ParameterizedTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.junit4.SnapshotClassRule; 4 | import au.com.origin.snapshots.junit4.SnapshotRule; 5 | import java.util.Arrays; 6 | import org.junit.ClassRule; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.TestName; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.Parameterized; 12 | import org.junit.runners.Parameterized.Parameters; 13 | 14 | @RunWith(Parameterized.class) 15 | public class ParameterizedTest { 16 | 17 | @ClassRule public static SnapshotClassRule snapshotClassRule = new SnapshotClassRule(); 18 | @Rule public SnapshotRule snapshotRule = new SnapshotRule(snapshotClassRule); 19 | @Rule public TestName testName = new TestName(); 20 | 21 | private Expect expect; 22 | 23 | @Parameters(name = "letter is {0}") 24 | public static Iterable data() { 25 | return Arrays.asList(new Object[][] {{"a"}, {"b"}, {"c"}}); 26 | } 27 | 28 | private String input; 29 | 30 | public ParameterizedTest(String input) { 31 | this.input = input; 32 | } 33 | 34 | @Test 35 | public void test() { 36 | expect.scenario(input).toMatchSnapshot(input); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/SnapshotRuleUsedTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName; 4 | import au.com.origin.snapshots.junit4.SnapshotClassRule; 5 | import au.com.origin.snapshots.junit4.SnapshotRule; 6 | import org.junit.ClassRule; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | 10 | public class SnapshotRuleUsedTest { 11 | 12 | @ClassRule public static SnapshotClassRule snapshotClassRule = new SnapshotClassRule(); 13 | 14 | @Rule public SnapshotRule snapshotRule = new SnapshotRule(snapshotClassRule); 15 | 16 | private Expect expect; 17 | 18 | @Test 19 | public void shouldUseExtensionViaInstanceVariable() { 20 | this.expect.toMatchSnapshot("Hello World"); 21 | } 22 | 23 | @Test 24 | public void shouldUseExtensionAgainViaInstanceVariable() { 25 | this.expect.toMatchSnapshot("Hello World"); 26 | } 27 | 28 | @SnapshotName("hello_world") 29 | @Test 30 | public void shouldUseExtensionWithSnapshotName() { 31 | expect.toMatchSnapshot("Hello World"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/SnapshotRunnerUsedTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName; 4 | import au.com.origin.snapshots.junit4.SnapshotRunner; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | @RunWith(SnapshotRunner.class) 9 | public class SnapshotRunnerUsedTest { 10 | 11 | private Expect expect; 12 | 13 | @Test 14 | public void shouldUseExtension(Expect expect) { 15 | expect.toMatchSnapshot("Hello World"); 16 | } 17 | 18 | @Test 19 | public void shouldUseExtensionAgain(Expect expect) { 20 | expect.toMatchSnapshot("Hello World"); 21 | } 22 | 23 | @Test 24 | public void shouldUseExtensionViaInstanceVariable() { 25 | this.expect.toMatchSnapshot("Hello World"); 26 | } 27 | 28 | @Test 29 | public void shouldUseExtensionAgainViaInstanceVariable() { 30 | this.expect.toMatchSnapshot("Hello World"); 31 | } 32 | 33 | @SnapshotName("hello_world") 34 | @Test 35 | public void shouldUseExtensionWithSnapshotName() { 36 | expect.toMatchSnapshot("Hello World"); 37 | } 38 | 39 | @SnapshotName("hello_world_again") 40 | @Test 41 | public void shouldUseExtensionAgainWithSnapshotName(Expect expect) { 42 | expect.toMatchSnapshot("Hello World"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/BaseClassTest$NestedClass.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.BaseClassTest$NestedClass.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/MyTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.MyTest.someTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/ParameterizedTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.ParameterizedTest.test[a]=[ 2 | a 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.ParameterizedTest.test[b]=[ 7 | b 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.ParameterizedTest.test[c]=[ 12 | c 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotContextRuleUsedTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtension=[ 2 | Hello World 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtensionAgain=[ 7 | Hello World 8 | Hello World Again 9 | ] 10 | 11 | 12 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtensionAgainViaInstanceVariable=[ 13 | Hello World 14 | Hello World Again 15 | ] 16 | 17 | 18 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtensionViaInstanceVariable=[ 19 | Hello World 20 | ] 21 | 22 | 23 | hello_world=[ 24 | Hello World 25 | ] 26 | 27 | 28 | hello_world_again=[ 29 | Hello World 30 | Hello World Again 31 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotRuleUsedTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtensionAgainViaInstanceVariable=[ 2 | Hello World 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotRuleUsedTest.shouldUseExtensionViaInstanceVariable=[ 7 | Hello World 8 | ] 9 | 10 | 11 | hello_world=[ 12 | Hello World 13 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotRunnerUsedTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotRunnerUsedTest.shouldUseExtension=[ 2 | Hello World 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotRunnerUsedTest.shouldUseExtensionAgain=[ 7 | Hello World 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.SnapshotRunnerUsedTest.shouldUseExtensionAgainViaInstanceVariable=[ 12 | Hello World 13 | ] 14 | 15 | 16 | au.com.origin.snapshots.SnapshotRunnerUsedTest.shouldUseExtensionViaInstanceVariable=[ 17 | Hello World 18 | ] 19 | 20 | 21 | hello_world=[ 22 | Hello World 23 | ] 24 | 25 | 26 | hello_world_again=[ 27 | Hello World 28 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/docs/JUnit4Example.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.annotations.SnapshotName; 5 | import au.com.origin.snapshots.junit4.SnapshotRunner; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | // Ensure you RunWith the SnapshotRunner 10 | @RunWith(SnapshotRunner.class) 11 | public class JUnit4Example { 12 | 13 | // Option 1: inject Expect as an instance variable 14 | private Expect expect; 15 | 16 | @SnapshotName("my first test") 17 | @Test 18 | public void myTest1() { 19 | // Verify your snapshot 20 | expect.toMatchSnapshot("Hello World"); 21 | } 22 | 23 | @SnapshotName("my second test") 24 | @Test 25 | // Option 2: inject Expect into the method signature 26 | public void myTest2(Expect expect) { 27 | expect.toMatchSnapshot("Hello World Again"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/docs/JUnit4RulesExample.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.annotations.SnapshotName; 5 | import au.com.origin.snapshots.junit4.SnapshotClassRule; 6 | import au.com.origin.snapshots.junit4.SnapshotRule; 7 | import org.junit.ClassRule; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | 11 | public class JUnit4RulesExample { 12 | 13 | @ClassRule public static SnapshotClassRule snapshotClassRule = new SnapshotClassRule(); 14 | 15 | @Rule public SnapshotRule snapshotRule = new SnapshotRule(snapshotClassRule); 16 | 17 | private Expect expect; 18 | 19 | @SnapshotName("my first test") 20 | @Test 21 | public void myTest1() { 22 | // Verify your snapshot 23 | expect.toMatchSnapshot("Hello World"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/docs/__snapshots__/JUnit4Example.snap: -------------------------------------------------------------------------------- 1 | my first test=[ 2 | Hello World 3 | ] 4 | 5 | 6 | my second test=[ 7 | Hello World Again 8 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/java/au/com/origin/snapshots/docs/__snapshots__/JUnit4RulesExample.snap: -------------------------------------------------------------------------------- 1 | my first test=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit4/src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer=au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer 2 | comparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator 3 | reporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter 4 | snapshot-dir=__snapshots__ 5 | output-dir=src/test/java 6 | ci-env-var=CI 7 | update-snapshot=none -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../gradle/publishing.gradle" 2 | apply from: "../gradle/spotless.gradle" 3 | 4 | ext { 5 | junitVersion = '5.7.2' 6 | } 7 | 8 | dependencies { 9 | implementation project(':java-snapshot-testing-core') 10 | 11 | // User supplied Junit5 version 12 | compileOnly "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}" 13 | compileOnly "org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}" 14 | 15 | // Testing 16 | testImplementation 'org.slf4j:slf4j-simple:2.0.0-alpha0' 17 | testImplementation "org.junit.jupiter:junit-jupiter-params:${project.junitVersion}" 18 | testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}" 19 | testImplementation "org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}" 20 | testImplementation 'org.assertj:assertj-core:3.11.1' 21 | 22 | // Required java-snapshot-testing peer dependencies 23 | testImplementation project(':java-snapshot-testing-plugin-jackson') 24 | testImplementation 'com.fasterxml.jackson.core:jackson-core:2.11.3' 25 | testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.11.3' 26 | } 27 | 28 | test { useJUnitPlatform() } 29 | 30 | publishing { 31 | publications { 32 | myPublication(MavenPublication) { 33 | artifact shadowJar 34 | groupId 'io.github.origin-energy' 35 | artifactId 'java-snapshot-testing-junit5' 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/BaseClassTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.junit5.SnapshotExtension; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | @ExtendWith({SnapshotExtension.class}) 9 | public class BaseClassTest { 10 | 11 | class TestBase { 12 | Expect expect; 13 | } 14 | 15 | @Nested 16 | @ExtendWith(SnapshotExtension.class) 17 | class NestedClass extends TestBase { 18 | 19 | @Test 20 | public void helloWorldTest() { 21 | expect.toMatchSnapshot("Hello World"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/NestedClassTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.junit5.SnapshotExtension; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import org.junit.jupiter.api.AfterAll; 10 | import org.junit.jupiter.api.Nested; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | 14 | @ExtendWith({SnapshotExtension.class}) 15 | public class NestedClassTest { 16 | 17 | @AfterAll 18 | public static void afterAll() { 19 | Path path = 20 | Paths.get("src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTest.snap"); 21 | assertThat(Files.exists(path)).isFalse(); 22 | } 23 | 24 | @Nested 25 | class NestedClassWithExpectArgument { 26 | 27 | @Test 28 | public void helloWorldTest(Expect expect) { 29 | expect.toMatchSnapshot("Hello World"); 30 | } 31 | } 32 | 33 | @Nested 34 | class NestedClassWithoutSnapshot { 35 | 36 | @Test 37 | public void helloWorldTest() { 38 | assertThat(true).isTrue(); 39 | } 40 | } 41 | 42 | @Nested 43 | class NestedClassWithExpectInstance { 44 | 45 | Expect expect; 46 | 47 | @Test 48 | public void helloWorldTest() { 49 | expect.toMatchSnapshot("Hello World"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/NestedClassTestWithExtends.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.junit5.SnapshotExtension; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import org.junit.jupiter.api.AfterAll; 10 | import org.junit.jupiter.api.Nested; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | 14 | public class NestedClassTestWithExtends { 15 | 16 | @AfterAll 17 | public static void afterAll() { 18 | Path path = 19 | Paths.get( 20 | "src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTestWithExtends.snap"); 21 | assertThat(Files.exists(path)).isFalse(); 22 | } 23 | 24 | @ExtendWith(SnapshotExtension.class) 25 | @Nested 26 | class NestedClass { 27 | 28 | Expect expect; 29 | 30 | @Test 31 | public void helloWorldTest() { 32 | expect.toMatchSnapshot("Hello World"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/SnapshotExtensionUsedTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName; 4 | import au.com.origin.snapshots.junit5.SnapshotExtension; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | @ExtendWith(SnapshotExtension.class) 9 | public class SnapshotExtensionUsedTest { 10 | 11 | private Expect expect; 12 | 13 | @Test 14 | public void shouldUseExtension(Expect expect) { 15 | expect.toMatchSnapshot("Hello World"); 16 | } 17 | 18 | @Test 19 | public void shouldUseExtensionAgain(Expect expect) { 20 | expect.toMatchSnapshot("Hello World"); 21 | } 22 | 23 | @Test 24 | public void shouldUseExtensionViaInstanceVariable() { 25 | expect.toMatchSnapshot("Hello World"); 26 | } 27 | 28 | @Test 29 | public void shouldUseExtensionAgainViaInstanceVariable() { 30 | expect.toMatchSnapshot("Hello World"); 31 | } 32 | 33 | @SnapshotName("hello_world") 34 | @Test 35 | public void snapshotWithName() { 36 | expect.toMatchSnapshot("Hello World"); 37 | } 38 | 39 | @SnapshotName("hello_world_2") 40 | @Test 41 | public void snapshotWithNameAgain() { 42 | expect.toMatchSnapshot("Hello World"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/SnapshotParameterTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots; 2 | 3 | import au.com.origin.snapshots.jackson.serializers.v1.JacksonSnapshotSerializer; 4 | import au.com.origin.snapshots.junit5.SnapshotExtension; 5 | import java.util.stream.Stream; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.Arguments; 9 | import org.junit.jupiter.params.provider.MethodSource; 10 | 11 | @ExtendWith({SnapshotExtension.class}) 12 | class SnapshotParameterTest { 13 | 14 | private Expect expect; 15 | 16 | static Stream testData() { 17 | 18 | return Stream.of( 19 | Arguments.of("Scenario2", "test input 1"), 20 | Arguments.of("Scenario2", "test input 1"), 21 | Arguments.of("Scenario2", "test input 1"), 22 | Arguments.of("Scenario3", "test input 2"), 23 | Arguments.of("Scenario3", "test input 2")); 24 | } 25 | 26 | @ParameterizedTest 27 | @MethodSource("au.com.origin.snapshots.SnapshotParameterTest#testData") 28 | void shouldSupportParameterizedTest(String scenario, String testInput, Expect expect) { 29 | expect.toMatchSnapshot("Duplicates are OK"); 30 | expect.toMatchSnapshot("Duplicates are OK"); 31 | expect.scenario("Scenario1").toMatchSnapshot("Additional snapshots need to include a scenario"); 32 | expect 33 | .serializer(JacksonSnapshotSerializer.class) 34 | .scenario(scenario) 35 | .toMatchSnapshot(testInput); 36 | } 37 | 38 | @ParameterizedTest 39 | @MethodSource("au.com.origin.snapshots.SnapshotParameterTest#testData") 40 | void shouldSupportParameterizedTestViaInstanceVariable(String scenario, String testInput) { 41 | this.expect.toMatchSnapshot("Duplicates are OK"); 42 | this.expect.toMatchSnapshot("Duplicates are OK"); 43 | this.expect 44 | .scenario("Scenario1") 45 | .toMatchSnapshot("Additional snapshots need to include a scenario"); 46 | this.expect 47 | .serializer(JacksonSnapshotSerializer.class) 48 | .scenario(scenario) 49 | .toMatchSnapshot(testInput); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/BaseClassTest$NestedClass.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.BaseClassTest$NestedClass.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTest$NestedClassWithExpectArgument.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.NestedClassTest$NestedClassWithExpectArgument.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTest$NestedClassWithExpectInstance.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.NestedClassTest$NestedClassWithExpectInstance.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTest$NestedClassWithoutSnapshot.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.NestedClassTest$NestedClassWithoutSnapshot.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/NestedClassTestWithExtends$NestedClass.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.NestedClassTestWithExtends$NestedClass.helloWorldTest=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotExtensionUsedTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotExtensionUsedTest.shouldUseExtension=[ 2 | Hello World 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotExtensionUsedTest.shouldUseExtensionAgain=[ 7 | Hello World 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.SnapshotExtensionUsedTest.shouldUseExtensionAgainViaInstanceVariable=[ 12 | Hello World 13 | ] 14 | 15 | 16 | au.com.origin.snapshots.SnapshotExtensionUsedTest.shouldUseExtensionViaInstanceVariable=[ 17 | Hello World 18 | ] 19 | 20 | 21 | hello_world=[ 22 | Hello World 23 | ] 24 | 25 | 26 | hello_world_2=[ 27 | Hello World 28 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/__snapshots__/SnapshotParameterTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTest=[ 2 | Duplicates are OK 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTestViaInstanceVariable=[ 7 | Duplicates are OK 8 | ] 9 | 10 | 11 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTestViaInstanceVariable[Scenario1]=[ 12 | Additional snapshots need to include a scenario 13 | ] 14 | 15 | 16 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTestViaInstanceVariable[Scenario2]=[ 17 | "test input 1" 18 | ] 19 | 20 | 21 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTestViaInstanceVariable[Scenario3]=[ 22 | "test input 2" 23 | ] 24 | 25 | 26 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTest[Scenario1]=[ 27 | Additional snapshots need to include a scenario 28 | ] 29 | 30 | 31 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTest[Scenario2]=[ 32 | "test input 1" 33 | ] 34 | 35 | 36 | au.com.origin.snapshots.SnapshotParameterTest.shouldSupportParameterizedTest[Scenario3]=[ 37 | "test input 2" 38 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/CustomFrameworkExample.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.SnapshotVerifier; 5 | import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.TestInfo; 10 | 11 | // Notice we aren't using any framework extensions 12 | public class CustomFrameworkExample { 13 | 14 | private static SnapshotVerifier snapshotVerifier; 15 | 16 | @BeforeAll 17 | static void beforeAll() { 18 | snapshotVerifier = 19 | new SnapshotVerifier(new PropertyResolvingSnapshotConfig(), CustomFrameworkExample.class); 20 | } 21 | 22 | @AfterAll 23 | static void afterAll() { 24 | snapshotVerifier.validateSnapshots(); 25 | } 26 | 27 | @Test 28 | void shouldMatchSnapshotOne(TestInfo testInfo) { 29 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 30 | expect.toMatchSnapshot("Hello World"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/CustomSnapshotConfigExample.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.annotations.UseSnapshotConfig; 5 | import au.com.origin.snapshots.junit5.SnapshotExtension; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | 9 | @ExtendWith(SnapshotExtension.class) 10 | // apply your custom snapshot configuration to this test class 11 | @UseSnapshotConfig(LowercaseToStringSnapshotConfig.class) 12 | public class CustomSnapshotConfigExample { 13 | 14 | @Test 15 | public void myTest(Expect expect) { 16 | expect.toMatchSnapshot("hello world"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/JUnit5Example.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.junit5.SnapshotExtension; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | // Ensure you extend your test class with the SnapshotExtension 9 | @ExtendWith({SnapshotExtension.class}) 10 | public class JUnit5Example { 11 | 12 | // Option 1: inject Expect as an instance variable 13 | private Expect expect; 14 | 15 | @Test 16 | public void myTest1() { 17 | // Verify your snapshot 18 | expect.toMatchSnapshot("Hello World"); 19 | } 20 | 21 | // Option 2: inject Expect into the method signature 22 | @Test 23 | public void myTest2(Expect expect) { 24 | expect.toMatchSnapshot("Hello World Again"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/JUnit5ResolutionHierarchyExample.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.annotations.UseSnapshotConfig; 5 | import au.com.origin.snapshots.junit5.SnapshotExtension; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | 9 | @ExtendWith(SnapshotExtension.class) 10 | @UseSnapshotConfig(LowercaseToStringSnapshotConfig.class) 11 | public class JUnit5ResolutionHierarchyExample { 12 | 13 | private Expect expect; 14 | 15 | @Test 16 | public void aliasMethodTest() { 17 | expect 18 | .serializer("json") // <------ Using snapshot.properties 19 | .toMatchSnapshot(new TestObject()); 20 | } 21 | 22 | @Test 23 | public void customSerializerTest() { 24 | expect 25 | .serializer(UppercaseToStringSerializer.class) // <------ Using custom serializer 26 | .toMatchSnapshot(new TestObject()); 27 | } 28 | 29 | // Read from LowercaseToStringSnapshotConfig defined on the class 30 | @Test 31 | public void lowercaseTest() { 32 | expect.toMatchSnapshot(new TestObject()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/LowercaseToStringSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import au.com.origin.snapshots.serializers.SerializerType; 6 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 7 | 8 | public class LowercaseToStringSerializer implements SnapshotSerializer { 9 | @Override 10 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 11 | return gen.toSnapshot(object.toString().toLowerCase()); 12 | } 13 | 14 | @Override 15 | public String getOutputFormat() { 16 | return SerializerType.TEXT.name(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/LowercaseToStringSnapshotConfig.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig; 4 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 5 | 6 | public class LowercaseToStringSnapshotConfig extends PropertyResolvingSnapshotConfig { 7 | 8 | @Override 9 | public SnapshotSerializer getSerializer() { 10 | return new LowercaseToStringSerializer(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/MyFirstSnapshotTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.annotations.SnapshotName; 5 | import au.com.origin.snapshots.junit5.SnapshotExtension; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | 11 | @ExtendWith({SnapshotExtension.class}) 12 | public class MyFirstSnapshotTest { 13 | 14 | private Expect expect; 15 | 16 | @SnapshotName("i_can_give_custom_names_to_my_snapshots") 17 | @Test 18 | public void toStringSerializationTest() { 19 | expect.toMatchSnapshot("Hello World"); 20 | } 21 | 22 | @Test 23 | public void jsonSerializationTest() { 24 | Map map = new HashMap<>(); 25 | map.put("name", "John Doe"); 26 | map.put("age", 40); 27 | 28 | expect.serializer("json").toMatchSnapshot(map); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/TestObject.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | public class TestObject { 4 | @Override 5 | public String toString() { 6 | return "This is a snapshot of the toString() method"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/UppercaseToStringSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import au.com.origin.snapshots.serializers.SerializerType; 6 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 7 | 8 | public class UppercaseToStringSerializer implements SnapshotSerializer { 9 | @Override 10 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 11 | return gen.toSnapshot(object.toString().toUpperCase()); 12 | } 13 | 14 | @Override 15 | public String getOutputFormat() { 16 | return SerializerType.TEXT.name(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/CustomFrameworkExample.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.CustomFrameworkExample.shouldMatchSnapshotOne=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/CustomSnapshotConfigExample.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.CustomSnapshotConfigExample.myTest=hello world -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/CustomSnapshotContextConfigExample.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.CustomSnapshotConfigExample.myTest=hello world -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/JUnit5Example.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.JUnit5Example.myTest1=[ 2 | Hello World 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.docs.JUnit5Example.myTest2=[ 7 | Hello World Again 8 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/JUnit5ResolutionHierarchyExample.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.JUnit5ResolutionHierarchyExample.aliasMethodTest=[ 2 | { } 3 | ] 4 | 5 | 6 | au.com.origin.snapshots.docs.JUnit5ResolutionHierarchyExample.customSerializerTest=THIS IS A SNAPSHOT OF THE TOSTRING() METHOD 7 | 8 | 9 | au.com.origin.snapshots.docs.JUnit5ResolutionHierarchyExample.lowercaseTest=this is a snapshot of the tostring() method -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/MyFirstSnapshotContextTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.MyFirstSnapshotTest.jsonSerializationTest=[ 2 | { 3 | "age": 40, 4 | "name": "John Doe" 5 | } 6 | ] 7 | 8 | 9 | i_can_give_custom_names_to_my_snapshots=[ 10 | Hello World 11 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/java/au/com/origin/snapshots/docs/__snapshots__/MyFirstSnapshotTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.docs.MyFirstSnapshotTest.jsonSerializationTest=[ 2 | { 3 | "age": 40, 4 | "name": "John Doe" 5 | } 6 | ] 7 | 8 | 9 | i_can_give_custom_names_to_my_snapshots=[ 10 | Hello World 11 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.execution.parallel.enabled=true 2 | junit.jupiter.execution.parallel.mode.default=concurrent -------------------------------------------------------------------------------- /java-snapshot-testing-junit5/src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer=au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer 2 | serializer.json=au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer 3 | comparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator 4 | reporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter 5 | snapshot-dir=__snapshots__ 6 | output-dir=src/test/java 7 | ci-env-var=CI 8 | update-snapshot=none -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../gradle/publishing.gradle" 2 | apply from: "../gradle/spotless.gradle" 3 | 4 | dependencies { 5 | compileOnly project(':java-snapshot-testing-core') 6 | 7 | // Client needs to supply their own versions 8 | compileOnly 'com.fasterxml.jackson.core:jackson-core:2.11.3' 9 | compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.11.3' 10 | 11 | // User supplied Junit5 version 12 | compileOnly 'org.junit.jupiter:junit-jupiter-api:5.3.2' 13 | compileOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' 14 | 15 | // Testing 16 | testImplementation project(':java-snapshot-testing-core') 17 | testImplementation 'org.slf4j:slf4j-simple:2.0.0-alpha0' 18 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.3.2' 19 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' 20 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.3.2' 21 | testImplementation 'org.assertj:assertj-core:3.11.1' 22 | testImplementation 'org.skyscreamer:jsonassert:1.5.0' // For docs/ reporter example 23 | 24 | testImplementation 'com.fasterxml.jackson.core:jackson-core:2.16.0' 25 | testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0' 26 | testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.0' 27 | testRuntimeOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0' 28 | } 29 | 30 | test { useJUnitPlatform() } 31 | 32 | publishing { 33 | publications { 34 | myPublication(MavenPublication) { 35 | artifact shadowJar 36 | groupId 'io.github.origin-energy' 37 | artifactId 'java-snapshot-testing-plugin-jackson' 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/DeterministicCollectionModule.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.module.SimpleModule; 7 | import java.io.IOException; 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | import java.util.Objects; 11 | import java.util.stream.Collectors; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * Inspired by: 16 | * https://www.stubbornjava.com/posts/creating-a-somewhat-deterministic-jackson-objectmapper 17 | */ 18 | @Slf4j 19 | public class DeterministicCollectionModule extends SimpleModule { 20 | 21 | public DeterministicCollectionModule() { 22 | addSerializer(Collection.class, new CollectionSerializer()); 23 | } 24 | 25 | /** 26 | * Collections gets converted into a sorted Object[]. This then gets serialized using the default 27 | * Array serializer. 28 | */ 29 | private static class CollectionSerializer extends JsonSerializer { 30 | 31 | @Override 32 | public void serialize(Collection value, JsonGenerator gen, SerializerProvider serializers) 33 | throws IOException { 34 | Object[] sorted = convert(value); 35 | serializers.defaultSerializeValue(sorted, gen); 36 | } 37 | 38 | private Object[] convert(Collection value) { 39 | if (value == null || value.isEmpty()) { 40 | return Collections.emptyList().toArray(); 41 | } 42 | 43 | try { 44 | return value.stream() 45 | .filter(Objects::nonNull) 46 | .sorted() 47 | .collect(Collectors.toList()) 48 | .toArray(); 49 | } catch (ClassCastException ex) { 50 | log.warn( 51 | "Unable to sort() collection - this may result in a non deterministic snapshot.\n" 52 | + "Consider adding a custom serializer for this type via the JacksonSnapshotSerializer#configure() method.\n" 53 | + ex.getMessage()); 54 | return value.toArray(); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/DeterministicJacksonSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | * Attempts to deterministically render a snapshot. 8 | * 9 | *

This can help in situations where collections are rendering in a different order on subsequent 10 | * runs. 11 | * 12 | *

Note that collections will be ordered which mar or may not be desirable given your use case. 13 | */ 14 | @Deprecated 15 | @Slf4j 16 | public class DeterministicJacksonSnapshotSerializer 17 | extends au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer { 18 | public DeterministicJacksonSnapshotSerializer() { 19 | super(); 20 | LoggingHelper.deprecatedV5( 21 | log, 22 | "Update to `au.com.origin.snapshots.jackson.serializers.v1.DeterministicJacksonSnapshotSerializer` in `snapshot.properties`"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/JacksonSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers; 2 | 3 | import au.com.origin.snapshots.logging.LoggingHelper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | @Deprecated 7 | @Slf4j 8 | public class JacksonSnapshotSerializer 9 | extends au.com.origin.snapshots.jackson.serializers.v1.JacksonSnapshotSerializer { 10 | 11 | public JacksonSnapshotSerializer() { 12 | super(); 13 | LoggingHelper.deprecatedV5( 14 | log, 15 | "Update to `au.com.origin.snapshots.jackson.serializers.v1.JacksonSnapshotSerializer` in `snapshot.properties`"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/v1/DeterministicJacksonSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers.v1; 2 | 3 | import au.com.origin.snapshots.jackson.serializers.DeterministicCollectionModule; 4 | import com.fasterxml.jackson.databind.MapperFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | 7 | /** 8 | * Attempts to deterministically render a snapshot. 9 | * 10 | *

This can help in situations where collections are rendering in a different order on subsequent 11 | * runs. 12 | * 13 | *

Note that collections will be ordered which mar or may not be desirable given your use case. 14 | */ 15 | public class DeterministicJacksonSnapshotSerializer extends JacksonSnapshotSerializer { 16 | 17 | @Override 18 | public void configure(ObjectMapper objectMapper) { 19 | objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); 20 | objectMapper.registerModule(new DeterministicCollectionModule()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/v1/JacksonSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers.v1; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.SnapshotSerializerContext; 5 | import au.com.origin.snapshots.exceptions.SnapshotExtensionException; 6 | import au.com.origin.snapshots.serializers.SerializerType; 7 | import au.com.origin.snapshots.serializers.SnapshotSerializer; 8 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 9 | import com.fasterxml.jackson.annotation.JsonInclude; 10 | import com.fasterxml.jackson.core.PrettyPrinter; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.fasterxml.jackson.databind.SerializationFeature; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | public class JacksonSnapshotSerializer implements SnapshotSerializer { 17 | 18 | private final PrettyPrinter pp = new SnapshotPrettyPrinter(); 19 | private final ObjectMapper objectMapper = 20 | new ObjectMapper() { 21 | { 22 | this.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); 23 | this.enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID); 24 | this.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 25 | this.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); 26 | this.setSerializationInclusion(JsonInclude.Include.NON_NULL); 27 | 28 | if (shouldFindAndRegisterModules()) { 29 | this.findAndRegisterModules(); 30 | } 31 | 32 | this.setVisibility( 33 | this.getSerializationConfig() 34 | .getDefaultVisibilityChecker() 35 | .withFieldVisibility(JsonAutoDetect.Visibility.ANY) 36 | .withGetterVisibility(JsonAutoDetect.Visibility.NONE) 37 | .withSetterVisibility(JsonAutoDetect.Visibility.NONE) 38 | .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); 39 | JacksonSnapshotSerializer.this.configure(this); 40 | } 41 | }; 42 | 43 | /** 44 | * Override to customize the Jackson objectMapper 45 | * 46 | * @param objectMapper existing ObjectMapper 47 | */ 48 | public void configure(ObjectMapper objectMapper) {} 49 | 50 | /** 51 | * Override to control the registration of all available jackson modules within the classpath 52 | * which are locatable via JDK ServiceLoader facility, along with module-provided SPI. 53 | */ 54 | protected boolean shouldFindAndRegisterModules() { 55 | return true; 56 | } 57 | 58 | @Override 59 | public Snapshot apply(Object object, SnapshotSerializerContext gen) { 60 | try { 61 | List objects = Arrays.asList(object); 62 | String body = objectMapper.writer(pp).writeValueAsString(objects); 63 | return gen.toSnapshot(body); 64 | } catch (Exception e) { 65 | throw new SnapshotExtensionException("Jackson Serialization failed", e); 66 | } 67 | } 68 | 69 | @Override 70 | public String getOutputFormat() { 71 | return SerializerType.JSON.name(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/main/java/au/com/origin/snapshots/jackson/serializers/v1/SnapshotPrettyPrinter.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.serializers.v1; 2 | 3 | import com.fasterxml.jackson.core.util.DefaultIndenter; 4 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; 5 | 6 | class SnapshotPrettyPrinter extends DefaultPrettyPrinter { 7 | 8 | public SnapshotPrettyPrinter() { 9 | super(""); 10 | Indenter lfOnlyIndenter = new DefaultIndenter(" ", "\n"); 11 | this.indentArraysWith(lfOnlyIndenter); 12 | this.indentObjectsWith(lfOnlyIndenter); 13 | 14 | this._objectFieldValueSeparatorWithSpaces = 15 | this._separators.getObjectFieldValueSeparator() + " "; 16 | } 17 | 18 | // It's a requirement 19 | // @see https://github.com/FasterXML/jackson-databind/issues/2203 20 | public DefaultPrettyPrinter createInstance() { 21 | return new DefaultPrettyPrinter(this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/NoNameChangeTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializer; 6 | import au.com.origin.snapshots.jackson.serializers.JacksonSnapshotSerializer; 7 | import org.junit.jupiter.api.Test; 8 | 9 | /** 10 | * These classes are likely defined in snapshot.properties as a string. 11 | * 12 | *

The clients IDE will not complain if they change so ensure they don't 13 | */ 14 | public class NoNameChangeTest { 15 | 16 | @Test 17 | public void serializersApiShouldNotChange() { 18 | assertThat(JacksonSnapshotSerializer.class.getName()) 19 | .isEqualTo("au.com.origin.snapshots.jackson.serializers.JacksonSnapshotSerializer"); 20 | assertThat(DeterministicJacksonSnapshotSerializer.class.getName()) 21 | .isEqualTo( 22 | "au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializer"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/ReflectionUtilities.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson; 2 | 3 | import au.com.origin.snapshots.exceptions.SnapshotMatchException; 4 | import java.lang.reflect.Method; 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | public class ReflectionUtilities { 9 | 10 | // FIXME consider guava reflection instead 11 | public static Method getMethod(Class clazz, String methodName) { 12 | try { 13 | return Stream.of(clazz.getDeclaredMethods()) 14 | .filter(method -> method.getName().equals(methodName)) 15 | .findFirst() 16 | .orElseThrow(() -> new NoSuchMethodException("Not Found")); 17 | } catch (NoSuchMethodException e) { 18 | return Optional.ofNullable(clazz.getSuperclass()) 19 | .map(superclass -> getMethod(superclass, methodName)) 20 | .orElseThrow( 21 | () -> 22 | new SnapshotMatchException( 23 | "Could not find method " 24 | + methodName 25 | + " on class " 26 | + clazz 27 | + "\nPlease annotate your test method with @Test and make it without any parameters!")); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.docs; 2 | 3 | import java.time.Instant; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | // Example base class used by all hibernate entities 8 | @Data 9 | @AllArgsConstructor 10 | public class BaseEntity { 11 | private Long id; 12 | private Instant createdDate; 13 | private Instant lastModifiedDate; 14 | private String somethingElse; 15 | } 16 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/CustomSerializerTest.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.docs; 2 | 3 | import au.com.origin.snapshots.Expect; 4 | import au.com.origin.snapshots.SnapshotVerifier; 5 | import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig; 6 | import java.time.Instant; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.TestInfo; 9 | 10 | public class CustomSerializerTest { 11 | 12 | @Test 13 | public void test1(TestInfo testInfo) { 14 | SnapshotVerifier snapshotVerifier = 15 | new SnapshotVerifier( 16 | new PropertyResolvingSnapshotConfig(), testInfo.getTestClass().get(), false); 17 | 18 | Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get()); 19 | expect 20 | .serializer(HibernateSnapshotSerializer.class) 21 | .toMatchSnapshot(new BaseEntity(1L, Instant.now(), Instant.now(), "This should render")); 22 | 23 | snapshotVerifier.validateSnapshots(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/HibernateSnapshotSerializer.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.docs; 2 | 3 | import au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializer; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonIgnoreType; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import java.time.Instant; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class HibernateSnapshotSerializer extends DeterministicJacksonSnapshotSerializer { 12 | 13 | @Override 14 | public void configure(ObjectMapper objectMapper) { 15 | super.configure(objectMapper); 16 | 17 | // Ignore Hibernate Lists to prevent infinite recursion 18 | objectMapper.addMixIn(List.class, IgnoreTypeMixin.class); 19 | objectMapper.addMixIn(Set.class, IgnoreTypeMixin.class); 20 | 21 | // Ignore Fields that Hibernate generates for us automatically 22 | objectMapper.addMixIn(BaseEntity.class, IgnoreHibernateEntityFields.class); 23 | } 24 | 25 | @JsonIgnoreType 26 | class IgnoreTypeMixin {} 27 | 28 | abstract class IgnoreHibernateEntityFields { 29 | @JsonIgnore 30 | abstract Long getId(); 31 | 32 | @JsonIgnore 33 | abstract Instant getCreatedDate(); 34 | 35 | @JsonIgnore 36 | abstract Instant getLastModifiedDate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/JsonAssertReporter.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.docs; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.reporters.SnapshotReporter; 5 | import au.com.origin.snapshots.serializers.SerializerType; 6 | import lombok.SneakyThrows; 7 | import org.skyscreamer.jsonassert.JSONAssert; 8 | import org.skyscreamer.jsonassert.JSONCompareMode; 9 | 10 | public class JsonAssertReporter implements SnapshotReporter { 11 | @Override 12 | public boolean supportsFormat(String outputFormat) { 13 | return SerializerType.JSON.name().equalsIgnoreCase(outputFormat); 14 | } 15 | 16 | @Override 17 | @SneakyThrows 18 | public void report(Snapshot previous, Snapshot current) { 19 | JSONAssert.assertEquals(previous.getBody(), current.getBody(), JSONCompareMode.STRICT); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/JsonObjectComparator.java: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.jackson.docs; 2 | 3 | import au.com.origin.snapshots.Snapshot; 4 | import au.com.origin.snapshots.comparators.SnapshotComparator; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import lombok.SneakyThrows; 7 | 8 | public class JsonObjectComparator implements SnapshotComparator { 9 | @Override 10 | public boolean matches(Snapshot previous, Snapshot current) { 11 | return asObject(previous.getName(), previous.getBody()) 12 | .equals(asObject(current.getName(), current.getBody())); 13 | } 14 | 15 | @SneakyThrows 16 | private static Object asObject(String snapshotName, String json) { 17 | return new ObjectMapper().readValue(json.replaceFirst(snapshotName + "=", ""), Object.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/docs/__snapshots__/CustomSerializerTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.jackson.docs.CustomSerializerTest.test1=[ 2 | { 3 | "somethingElse": "This should render" 4 | } 5 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/serializers/__snapshots__/DeterministicJacksonSnapshotSerializerTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializerTest.shouldSerializeDifferentTypes=[ 2 | { 3 | "aBoolean": true, 4 | "aByte": 65, 5 | "aChar": "A", 6 | "aDouble": 1.123456789123456, 7 | "aFloat": 0.1234567, 8 | "aLong": 9223372036854775807, 9 | "aShort": 32767, 10 | "anEnum": "A", 11 | "anEnumArray": [ 12 | "F", 13 | "A", 14 | "D", 15 | "E", 16 | "G", 17 | "B", 18 | "C" 19 | ], 20 | "anInt": 2147483647, 21 | "anObject": { }, 22 | "arrayList": [ 23 | "a", 24 | "b", 25 | "c", 26 | "d", 27 | "e", 28 | "f", 29 | "g" 30 | ], 31 | "date": "2020-10-19T22:21:07.103+00:00", 32 | "emptyOptional": null, 33 | "hashMap": { 34 | "a": 97, 35 | "b": 98, 36 | "c": 99, 37 | "d": 100, 38 | "e": 101, 39 | "f": 102, 40 | "g": 103 41 | }, 42 | "hashSet": [ 43 | "a", 44 | "b", 45 | "c", 46 | "d", 47 | "e", 48 | "f", 49 | "g" 50 | ], 51 | "linkedHashMap": { 52 | "a": 97, 53 | "b": 98, 54 | "c": 99, 55 | "d": 100, 56 | "e": 101, 57 | "f": 102, 58 | "g": 103 59 | }, 60 | "linkedHashSet": [ 61 | "a", 62 | "b", 63 | "c", 64 | "d", 65 | "e", 66 | "f", 67 | "g" 68 | ], 69 | "linkedList": [ 70 | "a", 71 | "b", 72 | "c", 73 | "d", 74 | "e", 75 | "f", 76 | "g" 77 | ], 78 | "listOfCollections": [ 79 | { 80 | "a": 97, 81 | "b": 98, 82 | "c": 99, 83 | "d": 100, 84 | "e": 101, 85 | "f": 102, 86 | "g": 103 87 | }, 88 | [ 89 | "a", 90 | "b", 91 | "c", 92 | "d", 93 | "e", 94 | "f", 95 | "g" 96 | ], 97 | [ 98 | "a", 99 | "b", 100 | "c", 101 | "d", 102 | "e", 103 | "f", 104 | "g" 105 | ] 106 | ], 107 | "localDate": "2020-10-19", 108 | "localDateTime": "2020-10-19T22:21:07.103", 109 | "presentOptional": "Hello World", 110 | "string": "Hello World", 111 | "stringArray": [ 112 | "f", 113 | "a", 114 | "d", 115 | "e", 116 | "g", 117 | "b", 118 | "c" 119 | ], 120 | "treeMap": { 121 | "a": 97, 122 | "b": 98, 123 | "c": 99, 124 | "d": 100, 125 | "e": 101, 126 | "f": 102, 127 | "g": 103 128 | }, 129 | "treeSet": [ 130 | "a", 131 | "b", 132 | "c", 133 | "d", 134 | "e", 135 | "f", 136 | "g" 137 | ], 138 | "zonedDateTime": "2020-04-19T22:21:07.103+10:00[Australia/Melbourne]" 139 | } 140 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/java/au/com/origin/snapshots/jackson/serializers/__snapshots__/JacksonSnapshotSerializerTest.snap: -------------------------------------------------------------------------------- 1 | au.com.origin.snapshots.jackson.serializers.JacksonSnapshotSerializerTest.shouldSerializeDifferentTypes=[ 2 | { 3 | "anObject": { }, 4 | "aByte": 65, 5 | "aShort": 32767, 6 | "anInt": 2147483647, 7 | "aLong": 9223372036854775807, 8 | "aFloat": 0.1234567, 9 | "aDouble": 1.123456789123456, 10 | "aBoolean": true, 11 | "aChar": "A", 12 | "string": "Hello World", 13 | "date": "2020-10-19T22:21:07.103+00:00", 14 | "localDate": "2020-10-19", 15 | "localDateTime": "2020-10-19T22:21:07.103", 16 | "zonedDateTime": "2020-04-19T22:21:07.103+10:00[Australia/Melbourne]", 17 | "anEnum": "A", 18 | "presentOptional": "Hello World", 19 | "emptyOptional": null, 20 | "stringArray": [ 21 | "f", 22 | "a", 23 | "d", 24 | "e", 25 | "g", 26 | "b", 27 | "c" 28 | ], 29 | "anEnumArray": [ 30 | "F", 31 | "A", 32 | "D", 33 | "E", 34 | "G", 35 | "B", 36 | "C" 37 | ], 38 | "hashMap": { 39 | "a": 97, 40 | "b": 98, 41 | "c": 99, 42 | "d": 100, 43 | "e": 101, 44 | "f": 102, 45 | "g": 103 46 | }, 47 | "treeMap": { 48 | "a": 97, 49 | "b": 98, 50 | "c": 99, 51 | "d": 100, 52 | "e": 101, 53 | "f": 102, 54 | "g": 103 55 | }, 56 | "linkedHashMap": { 57 | "a": 97, 58 | "b": 98, 59 | "c": 99, 60 | "d": 100, 61 | "e": 101, 62 | "f": 102, 63 | "g": 103 64 | }, 65 | "linkedHashSet": [ 66 | "f", 67 | "a", 68 | "d", 69 | "e", 70 | "g", 71 | "b", 72 | "c" 73 | ], 74 | "hashSet": [ 75 | "a", 76 | "b", 77 | "c", 78 | "d", 79 | "e", 80 | "f", 81 | "g" 82 | ], 83 | "treeSet": [ 84 | "a", 85 | "b", 86 | "c", 87 | "d", 88 | "e", 89 | "f", 90 | "g" 91 | ], 92 | "arrayList": [ 93 | "f", 94 | "a", 95 | "d", 96 | "e", 97 | "g", 98 | "b", 99 | "c" 100 | ], 101 | "linkedList": [ 102 | "f", 103 | "a", 104 | "d", 105 | "e", 106 | "g", 107 | "b", 108 | "c" 109 | ], 110 | "listOfCollections": [ 111 | { 112 | "a": 97, 113 | "b": 98, 114 | "c": 99, 115 | "d": 100, 116 | "e": 101, 117 | "f": 102, 118 | "g": 103 119 | }, 120 | [ 121 | "f", 122 | "a", 123 | "d", 124 | "e", 125 | "g", 126 | "b", 127 | "c" 128 | ], 129 | [ 130 | "f", 131 | "a", 132 | "d", 133 | "e", 134 | "g", 135 | "b", 136 | "c" 137 | ] 138 | ] 139 | } 140 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-plugin-jackson/src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer=au.com.origin.snapshots.jackson.serializers.JacksonSnapshotSerializer 2 | serializer.orderedJson=au.com.origin.snapshots.jackson.serializers.DeterministicJacksonSnapshotSerializer 3 | comparator=au.com.origin.snapshots.comparators.PlainTextEqualsComparator 4 | reporters=au.com.origin.snapshots.reporters.PlainTextSnapshotReporter 5 | snapshot-dir=__snapshots__ 6 | output-dir=src/test/java 7 | ci-env-var=CI 8 | update-snapshot=none -------------------------------------------------------------------------------- /java-snapshot-testing-spock/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | } 4 | 5 | apply from: "../gradle/publishing.gradle" 6 | apply from: "../gradle/spotless-groovy.gradle" 7 | 8 | dependencies { 9 | implementation project(':java-snapshot-testing-core') 10 | 11 | // User supplied Spock Version 12 | compileOnly 'org.codehaus.groovy:groovy-all:2.5.8' 13 | compileOnly 'org.spockframework:spock-core:1.3-groovy-2.5' 14 | 15 | // Testing 16 | testImplementation 'org.slf4j:slf4j-simple:2.0.0-alpha0' 17 | testImplementation 'org.codehaus.groovy:groovy-all:2.5.8' 18 | testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5' 19 | testImplementation 'org.assertj:assertj-core:3.11.1' 20 | 21 | // Required java-snapshot-testing peer dependencies 22 | testImplementation 'com.fasterxml.jackson.core:jackson-core:2.11.3' 23 | testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.11.3' 24 | } 25 | 26 | publishing { 27 | publications { 28 | myPublication(MavenPublication) { 29 | artifact shadowJar 30 | groupId 'io.github.origin-energy' 31 | artifactId 'java-snapshot-testing-spock' 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/main/groovy/au/com/origin/snapshots/spock/EnableSnapshots.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.spock 2 | 3 | import org.spockframework.runtime.extension.ExtensionAnnotation 4 | 5 | import java.lang.annotation.ElementType 6 | import java.lang.annotation.Retention 7 | import java.lang.annotation.RetentionPolicy 8 | import java.lang.annotation.Target 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target([ElementType.TYPE, ElementType.METHOD]) 12 | @ExtensionAnnotation(SnapshotExtension) 13 | @interface EnableSnapshots {} 14 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/main/groovy/au/com/origin/snapshots/spock/SnapshotExtension.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.spock 2 | 3 | import au.com.origin.snapshots.SnapshotVerifier 4 | import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig 5 | import au.com.origin.snapshots.config.SnapshotConfig 6 | import au.com.origin.snapshots.config.SnapshotConfigInjector 7 | import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension 8 | import org.spockframework.runtime.model.SpecInfo 9 | 10 | class SnapshotExtension extends AbstractAnnotationDrivenExtension implements SnapshotConfigInjector { 11 | 12 | SnapshotVerifier snapshotVerifier; 13 | 14 | void visitSpecAnnotation(EnableSnapshots annotation, SpecInfo spec) { 15 | this.snapshotVerifier = new SnapshotVerifier(getSnapshotConfig(), spec.reflection, false) 16 | } 17 | 18 | void visitSpec(SpecInfo spec) { 19 | def snapshotMethodInterceptor = new SnapshotMethodInterceptor(snapshotVerifier) 20 | spec.allFeatures.featureMethod*.addInterceptor(snapshotMethodInterceptor) 21 | spec.addCleanupSpecInterceptor(snapshotMethodInterceptor) 22 | } 23 | 24 | @Override 25 | SnapshotConfig getSnapshotConfig() { 26 | return new PropertyResolvingSnapshotConfig() 27 | } 28 | } -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/main/groovy/au/com/origin/snapshots/spock/SnapshotMethodInterceptor.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.spock 2 | 3 | import au.com.origin.snapshots.Expect 4 | import au.com.origin.snapshots.utils.ReflectionUtils 5 | import au.com.origin.snapshots.SnapshotVerifier 6 | import au.com.origin.snapshots.logging.LoggingHelper 7 | import org.slf4j.LoggerFactory 8 | import org.spockframework.runtime.extension.AbstractMethodInterceptor 9 | import org.spockframework.runtime.extension.IMethodInvocation 10 | 11 | import java.lang.reflect.Method 12 | 13 | // Based on this issue: https://github.com/spockframework/spock/issues/652 14 | class SnapshotMethodInterceptor extends AbstractMethodInterceptor { 15 | private log = LoggerFactory.getLogger( SnapshotMethodInterceptor.class ) 16 | private final SnapshotVerifier snapshotVerifier; 17 | 18 | SnapshotMethodInterceptor(SnapshotVerifier snapshotVerifier) { 19 | this.snapshotVerifier = snapshotVerifier 20 | } 21 | 22 | @Override 23 | void interceptFeatureMethod(IMethodInvocation invocation) throws Throwable { 24 | updateInstanceVariable(invocation.instance, invocation.feature.featureMethod.reflection) 25 | 26 | def parameterCount = invocation.method.reflection.parameterCount 27 | if (parameterCount > invocation.arguments.length) { 28 | def newArguments = new Object[parameterCount] 29 | System.arraycopy invocation.arguments, 0, newArguments, 0, invocation.arguments.length 30 | invocation.arguments = newArguments 31 | } 32 | invocation.method.reflection.parameterTypes.eachWithIndex { type, i -> 33 | if (Expect.class == type) { 34 | LoggingHelper.deprecatedV5(log, "Injecting 'Expect' via method a argument is no longer recommended. Consider using instance variable injection instead.") 35 | invocation.arguments[i] = new Expect(snapshotVerifier, invocation.feature.featureMethod.reflection) 36 | } 37 | } 38 | invocation.proceed() 39 | } 40 | 41 | private void updateInstanceVariable(Object testInstance, Method testMethod) { 42 | ReflectionUtils.findFieldByPredicate(testInstance.class, { field -> field.getType() == Expect.class }) 43 | .ifPresent({ field -> 44 | Expect expect = Expect.of(snapshotVerifier, testMethod); 45 | ReflectionUtils.makeAccessible(field); 46 | try { 47 | field.set(testInstance, expect); 48 | } catch (IllegalAccessException e) { 49 | throw new RuntimeException(e); 50 | } 51 | }); 52 | } 53 | 54 | @Override 55 | void interceptCleanupSpecMethod(IMethodInvocation invocation) throws Throwable { 56 | this.snapshotVerifier.validateSnapshots(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/SpecificationBase.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots 2 | 3 | import org.junit.runner.RunWith 4 | import org.spockframework.runtime.Sputnik 5 | import spock.lang.Specification 6 | 7 | @RunWith(Sputnik.class) 8 | class SpecificationBase extends Specification { 9 | Expect expect; 10 | } 11 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/SpockExtensionUsedSpec.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName 4 | import au.com.origin.snapshots.spock.EnableSnapshots 5 | import spock.lang.Specification 6 | import spock.lang.Unroll 7 | 8 | @EnableSnapshots 9 | class SpockExtensionUsedSpec extends Specification { 10 | 11 | Expect expect 12 | 13 | @SnapshotName("Should use extension") 14 | def "Should use extension"(Expect expect) { 15 | when: 16 | expect.toMatchSnapshot("Hello World") 17 | 18 | then: 19 | true 20 | } 21 | 22 | @SnapshotName("Should use extension again") 23 | def "Should use extension again"(Expect expect) { 24 | when: 25 | expect.toMatchSnapshot("Hello World") 26 | 27 | then: 28 | true 29 | } 30 | 31 | @SnapshotName("Should use extension via instance variable") 32 | def "Should use extension via instance variable"() { 33 | when: 34 | expect.toMatchSnapshot("Hello World") 35 | 36 | then: 37 | true 38 | } 39 | 40 | @SnapshotName("DataTable example 1") 41 | @Unroll 42 | def 'DataTable example 1: #letter'(def letter) { 43 | given: 'I use an @Unroll function' 44 | String result = letter.toUpperCase() 45 | 46 | when: 'I snapshot the letter' 47 | expect.scenario("letter $letter").toMatchSnapshot(result) 48 | 49 | then: 50 | true 51 | 52 | where: 53 | [letter] << [['A'],['B'],['C']] 54 | } 55 | 56 | 57 | @SnapshotName("DataTable example 2") 58 | def 'DataTable example 2: #scenario to uppercase'() { 59 | when: 'I convert to uppercase' 60 | String result = value.toUpperCase(); 61 | then: 'Should convert letters to uppercase' 62 | // Check you snapshot against your output using a unique scenario 63 | expect.scenario(scenario).toMatchSnapshot(result) 64 | where: 65 | scenario | value 66 | 'letter' | 'a' 67 | 'number' | '1' 68 | } 69 | 70 | @SnapshotName("Can run a non snapshot test") 71 | def "Can run a non snapshot test"() { 72 | when: 73 | def isTrue = true 74 | 75 | then: 76 | isTrue 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/TestBaseSpec.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots 2 | 3 | import au.com.origin.snapshots.annotations.SnapshotName 4 | import au.com.origin.snapshots.spock.EnableSnapshots 5 | 6 | @EnableSnapshots 7 | class TestBaseSpec extends SpecificationBase { 8 | 9 | @SnapshotName("Should use extension") 10 | def "Should use extension"() { 11 | when: 12 | expect.toMatchSnapshot("Hello World") 13 | 14 | then: 15 | true 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/__snapshots__/SpockExtensionUsedSpec.snap: -------------------------------------------------------------------------------- 1 | DataTable example 1[letter A]=[ 2 | A 3 | ] 4 | 5 | 6 | DataTable example 1[letter B]=[ 7 | B 8 | ] 9 | 10 | 11 | DataTable example 1[letter C]=[ 12 | C 13 | ] 14 | 15 | 16 | DataTable example 2[letter]=[ 17 | A 18 | ] 19 | 20 | 21 | DataTable example 2[number]=[ 22 | 1 23 | ] 24 | 25 | 26 | Should use extension again=[ 27 | Hello World 28 | ] 29 | 30 | 31 | Should use extension via instance variable=[ 32 | Hello World 33 | ] 34 | 35 | 36 | Should use extension=[ 37 | Hello World 38 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/__snapshots__/TestBaseSpec.snap: -------------------------------------------------------------------------------- 1 | Should use extension=[ 2 | Hello World 3 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/docs/SpockExample.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs 2 | 3 | import au.com.origin.snapshots.Expect 4 | import au.com.origin.snapshots.annotations.SnapshotName 5 | import au.com.origin.snapshots.spock.EnableSnapshots 6 | import spock.lang.Specification 7 | 8 | // Ensure you enable snapshot testing support 9 | @EnableSnapshots 10 | class SpockExample extends Specification { 11 | 12 | // Option 1: inject Expect as an instance variable 13 | private Expect expect 14 | 15 | // With spock tests you should always use @SnapshotName - otherwise they become coupled to test order 16 | @SnapshotName("should_use_extension") 17 | def "Should use extension"() { 18 | when: 19 | expect.toMatchSnapshot("Hello World") 20 | 21 | then: 22 | true 23 | } 24 | 25 | @SnapshotName("should_use_extension_as_method_argument") 26 | // Option 2: inject Expect into the method signature 27 | def "Should use extension as method argument"(Expect expect) { 28 | when: 29 | expect.toMatchSnapshot("Hello World") 30 | 31 | then: 32 | true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/docs/SpockWithParametersExample.groovy: -------------------------------------------------------------------------------- 1 | package au.com.origin.snapshots.docs 2 | 3 | import au.com.origin.snapshots.Expect 4 | import au.com.origin.snapshots.annotations.SnapshotName 5 | import au.com.origin.snapshots.spock.EnableSnapshots 6 | import spock.lang.Specification 7 | 8 | @EnableSnapshots 9 | class SpockWithParametersExample extends Specification { 10 | 11 | private Expect expect 12 | 13 | @SnapshotName("convert_to_uppercase") 14 | def 'Convert #scenario to uppercase'() { 15 | when: 'I convert to uppercase' 16 | String result = value.toUpperCase(); 17 | then: 'Should convert letters to uppercase' 18 | // Check you snapshot against your output using a unique scenario 19 | expect.scenario(scenario).toMatchSnapshot(result) 20 | where: 21 | scenario | value 22 | 'letter' | 'a' 23 | 'number' | '1' 24 | } 25 | } -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/docs/__snapshots__/SpockExample.snap: -------------------------------------------------------------------------------- 1 | should_use_extension=[ 2 | Hello World 3 | ] 4 | 5 | 6 | should_use_extension_as_method_argument=[ 7 | Hello World 8 | ] 9 | -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/groovy/au/com/origin/snapshots/docs/__snapshots__/SpockWithParametersExample.snap: -------------------------------------------------------------------------------- 1 | convert_to_uppercase[letter]=[ 2 | A 3 | ] 4 | 5 | 6 | convert_to_uppercase[number]=[ 7 | 1 8 | ] -------------------------------------------------------------------------------- /java-snapshot-testing-spock/src/test/resources/snapshot.properties: -------------------------------------------------------------------------------- 1 | serializer=au.com.origin.snapshots.serializers.v1.ToStringSnapshotSerializer 2 | comparator=au.com.origin.snapshots.comparators.v1.PlainTextEqualsComparator 3 | reporters=au.com.origin.snapshots.reporters.v1.PlainTextSnapshotReporter 4 | snapshot-dir=__snapshots__ 5 | output-dir=src/test/groovy 6 | ci-env-var=CI 7 | update-snapshot=none -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'java-snapshot-testing' 2 | 3 | include 'java-snapshot-testing-core', 'java-snapshot-testing-junit4', 'java-snapshot-testing-junit5', 'java-snapshot-testing-spock', 'java-snapshot-testing-plugin-jackson' --------------------------------------------------------------------------------