├── .github
└── workflows
│ ├── ci.yml
│ └── dependabot-workflow.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── examples
├── build.gradle
└── src
│ └── test
│ └── java
│ └── com
│ └── zendesk
│ └── jazon
│ └── junit
│ ├── ExampleTest.java
│ ├── ExamplesWithGuavaTest.java
│ └── ReadmeExamplesTest.java
├── gradle.properties
├── gradle
├── publishing.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jazon-core
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── zendesk
│ │ └── jazon
│ │ ├── MatchResult.java
│ │ ├── Matcher.java
│ │ ├── MatcherFactory.java
│ │ ├── actual
│ │ ├── Actual.java
│ │ ├── ActualJsonArray.java
│ │ ├── ActualJsonBoolean.java
│ │ ├── ActualJsonNull.java
│ │ ├── ActualJsonNumber.java
│ │ ├── ActualJsonObject.java
│ │ ├── ActualJsonString.java
│ │ └── factory
│ │ │ ├── ActualFactory.java
│ │ │ └── GsonActualFactory.java
│ │ ├── expectation
│ │ ├── Expectations.java
│ │ ├── JsonExpectation.java
│ │ ├── impl
│ │ │ ├── AnyNumberOf.java
│ │ │ ├── AnyNumberOfExpectation.java
│ │ │ ├── NullExpectation.java
│ │ │ ├── ObjectExpectation.java
│ │ │ ├── OrderedArrayExpectation.java
│ │ │ ├── PredicateExpectation.java
│ │ │ ├── PrimitiveValueExpectation.java
│ │ │ └── UnorderedArrayExpectation.java
│ │ └── translator
│ │ │ ├── DefaultTranslators.java
│ │ │ ├── JazonTypesTranslators.java
│ │ │ ├── Translator.java
│ │ │ ├── TranslatorFacade.java
│ │ │ └── TranslatorMapping.java
│ │ ├── mismatch
│ │ ├── Mismatch.java
│ │ ├── MismatchWithPath.java
│ │ ├── MismatchWithPathFactory.java
│ │ └── impl
│ │ │ ├── ArrayLackingElementsMismatch.java
│ │ │ ├── ArrayUnexpectedElementsMismatch.java
│ │ │ ├── NoFieldMismatch.java
│ │ │ ├── NotNullMismatch.java
│ │ │ ├── NullMismatch.java
│ │ │ ├── PredicateExecutionFailedMismatch.java
│ │ │ ├── PredicateMismatch.java
│ │ │ ├── PrimitiveValueMismatch.java
│ │ │ ├── TypeMismatch.java
│ │ │ └── UnexpectedFieldMismatch.java
│ │ └── util
│ │ └── Preconditions.java
│ └── test
│ ├── groovy
│ └── com
│ │ └── zendesk
│ │ └── jazon
│ │ ├── MatcherSpec.groovy
│ │ ├── MessagesSpec.groovy
│ │ ├── MismatchPathSpec.groovy
│ │ ├── actual
│ │ └── ActualJsonNumberSpec.groovy
│ │ └── mismatch
│ │ └── TypeMismatchSpec.groovy
│ └── java
│ └── com
│ └── zendesk
│ └── jazon
│ └── TestActualFactory.java
├── jazon-junit
├── README.md
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── zendesk
│ │ └── jazon
│ │ ├── expectation
│ │ └── translator
│ │ │ └── JunitTranslators.java
│ │ └── junit
│ │ ├── JazonJunitAdapter.java
│ │ ├── JazonList.java
│ │ ├── JazonMap.java
│ │ ├── JsonExpectationInput.java
│ │ ├── ObjectExpectationInput.java
│ │ └── PredicateExpectationInput.java
│ └── test
│ └── java
│ └── com
│ └── zendesk
│ └── jazon
│ └── junit
│ ├── JazonListTest.java
│ ├── JazonMapTest.java
│ └── JunitSpecificMatcherTest.java
├── jazon-spock
├── README.md
├── build.gradle
└── src
│ ├── main
│ ├── groovy
│ │ └── com
│ │ │ └── zendesk
│ │ │ └── jazon
│ │ │ └── spock
│ │ │ └── JazonSpockAdapter.groovy
│ └── java
│ │ └── com
│ │ └── zendesk
│ │ └── jazon
│ │ └── expectation
│ │ └── translator
│ │ └── SpockTranslators.java
│ └── test
│ └── groovy
│ └── com
│ └── zendesk
│ └── jazon
│ ├── ExampleSpec.groovy
│ ├── MatcherForGroovySpec.groovy
│ ├── MessagesForGroovySpec.groovy
│ └── spock
│ └── JazonSpockAdapterSpec.groovy
└── settings.gradle
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-22.04
14 | strategy:
15 | matrix:
16 | java: ['8']
17 | steps:
18 | - uses: zendesk/checkout@v2
19 | - name: Set up JDK
20 | uses: zendesk/setup-java@v1
21 | with:
22 | java-version: ${{ matrix.java }}
23 | - name: print Java version
24 | run: java -version
25 | - name: Build
26 | run: ./gradlew clean build
27 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-workflow.yml:
--------------------------------------------------------------------------------
1 | name: Update dependency graph
2 |
3 | on:
4 | push:
5 | branches: [ 'master' ]
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | update-dependency-graph:
12 | runs-on: [self-hosted, zendesk-stable]
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4.2.2
16 |
17 | - name: Set up JDK
18 | uses: actions/setup-java@v4.7.0
19 |
20 | with:
21 | distribution: 'corretto'
22 | java-version: '17'
23 |
24 | - name: Fix maven-repo.gradle
25 | run: |
26 | # Display content of the problematic file
27 | echo "Current content of maven-repo.gradle:"
28 | cat gradle/maven-repo.gradle
29 |
30 | # Create a temporary file with fixes
31 | cat > gradle/maven-repo.gradle.new << 'EOL'
32 | // Define the property as an extension property
33 | ext {
34 | baseMavenRepoAllUrl = System.getenv('BASE_MAVEN_REPO_ALL_URL') ?: "https://zdrepo.jfrog.io/zdrepo/zen-libs-m2/"
35 | }
36 |
37 | // Rest of the original file content can follow
38 | // ... keep any other needed content from the original file
39 | EOL
40 |
41 | # Replace the original file
42 | mv gradle/maven-repo.gradle.new gradle/maven-repo.gradle
43 |
44 | # Verify the change
45 | echo "Updated content of maven-repo.gradle:"
46 | cat gradle/maven-repo.gradle
47 |
48 | - name: Print Gradle info
49 | run: |
50 | ./gradlew --version
51 | ./gradlew properties
52 |
53 | - name: Generate and submit dependency graph
54 | uses: gradle/actions/dependency-submission@v4.3.0
55 | with:
56 | gradle-build-root: .
57 | gradle-build-module: .
58 | gradle-build-configuration: implementation
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ IDEA
2 | .idea/
3 | *.iml
4 | *.iws
5 | *.ipr
6 | .ideaDataSources/
7 |
8 | # Eclipse
9 | .classpath
10 | .project
11 | .settings/
12 |
13 | # Gradle
14 | build/
15 | .gradle/
16 | .gradletasknamecache
17 |
18 | # Misc
19 | bin/
20 | log/
21 | *.log
22 | out/
23 | .DS_Store
24 |
25 | # Does not ignore anything is source folders
26 | !**/src/**
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | script: ./gradlew clean build
3 | deploy:
4 | provider: script
5 | script: ./gradlew publish -PossrhUsername=${SECRET_OSSRH_USERNAME} -PossrhPassword=${SECRET_OSSRH_PASSWORD}
6 | skip_cleanup: true
7 | on:
8 | repo: zendesk/jazon
9 | all_branches: true
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jazon
2 | 
3 |
4 | A library for test assertions on JSON payloads.
5 |
6 | Supports Spock and JUnit. Easy to extend for other test frameworks and languages.
7 |
8 | ## About
9 |
10 | Jazon was created to make writing tests on JSON APIs easy. It offers:
11 | * Simple exact-match assertions on JSON
12 | * Matching unordered JSON arrays (ability to ignore the items order)
13 | * User-defined wildcard assertions, e.g.
14 | * Match string to a regex
15 | * Match number to a range
16 | * Match datetime to some specific format
17 | * Verify that float has only 2 decimal points
18 | * Verify that list has only 25 items
19 | * ... anything you need
20 | * Human-readable error messages for fast mismatch tracing
21 | * Optimised to minimise code duplication
22 |
23 | ## Using Jazon in your project
24 |
25 | Jazon is provided as separate libraries (so called adapters) for each supported testing framework.
26 | Depending on the framework you use, pick the adapter library that is applicable for you.
27 |
28 | ### Spock
29 |
30 | [User guide for Spock Adapter](jazon-spock/README.md#Quickstart)
31 |
32 | ##### Gradle:
33 | ```groovy
34 | dependencies {
35 | testCompile 'com.zendesk.jazon:jazon-spock:0.4.1'
36 | }
37 | ```
38 | ##### Maven:
39 | ```xml
40 |
41 | com.zendesk.jazon
42 | jazon-spock
43 | 0.4.1
44 | test
45 |
46 | ```
47 |
48 | ### JUnit:
49 |
50 | [User guide for JUnit Adapter](jazon-junit/README.md#Quickstart)
51 |
52 | ##### Gradle:
53 | ```groovy
54 | dependencies {
55 | testCompile 'com.zendesk.jazon:jazon-junit:0.4.1'
56 | }
57 | ```
58 | ##### Maven:
59 | ```xml
60 |
61 | com.zendesk.jazon
62 | jazon-junit
63 | 0.4.1
64 | test
65 |
66 | ```
67 |
68 | ## Copyright and license
69 | Copyright 2019 Zendesk, Inc.
70 |
71 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
72 | You may obtain a copy of the License at
73 | http://www.apache.org/licenses/LICENSE-2.0
74 |
75 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
76 |
77 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | group 'com.zendesk.jazon'
3 | }
4 |
5 | subprojects {
6 | apply plugin: 'groovy'
7 |
8 | sourceCompatibility = 1.8
9 |
10 | repositories {
11 | jcenter()
12 | }
13 |
14 | task sourceJar(type: Jar, dependsOn: classes) {
15 | classifier 'sources'
16 | from sourceSets.main.allSource
17 | }
18 |
19 | task javadocJar(type: Jar) {
20 | classifier = 'javadoc'
21 | from javadoc
22 | }
23 |
24 | artifacts {
25 | archives sourceJar, javadocJar
26 | }
27 |
28 | apply plugin: 'signing'
29 | apply plugin: 'maven-publish'
30 |
31 | signing {
32 | required { !isSnapshotVersion() }
33 | sign publishing.publications
34 | }
35 |
36 | publishing {
37 | repositories {
38 | maven {
39 | String snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
40 | String releaseUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
41 | url = isSnapshotVersion() ? snapshotUrl : releaseUrl
42 | credentials {
43 | username project.findProperty('ossrhUsername') ?: ''
44 | password project.findProperty('ossrhPassword') ?: ''
45 | }
46 | }
47 | }
48 | }
49 |
50 | publish {
51 | doLast {
52 | println "The published version: ${version}"
53 | }
54 | }
55 | }
56 |
57 | boolean isSnapshotVersion() {
58 | version.endsWith('SNAPSHOT')
59 | }
60 |
--------------------------------------------------------------------------------
/examples/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | testImplementation project(':jazon-junit')
3 | testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.1'
4 | testImplementation group: 'com.google.guava', name: 'guava', version: '27.1-jre'
5 | testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.1'
6 | }
7 |
8 | test {
9 | useJUnitPlatform()
10 | onlyIf {
11 | project.hasProperty('runExamples')
12 | }
13 | }
14 |
15 | publish {
16 | onlyIf { false }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/src/test/java/com/zendesk/jazon/junit/ExampleTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static com.zendesk.jazon.junit.JazonJunitAdapter.assertThat;
6 |
7 | class ExampleTest {
8 |
9 | /**
10 | * This is a passing test example.
11 | */
12 | @Test
13 | void testRegex() {
14 | // given
15 | String actualJson = "{" +
16 | " \"first\": \"blue\"," +
17 | " \"second\": \"black\"," +
18 | " \"third\": \"red\"" +
19 | "}";
20 |
21 | // then
22 | assertThat(actualJson).matches(
23 | new JazonMap()
24 | .with("first", (String s) -> s.matches("bl.*"))
25 | .with("second", s -> ((String) s).matches("bl.*"))
26 | .with("third", "red")
27 | );
28 | }
29 |
30 | /**
31 | * This is a failing test example.
32 | */
33 | @Test
34 | void testRegexTypeMismatch() {
35 | // given
36 | String actualJson = "{" +
37 | " \"first\": 55" +
38 | "}";
39 |
40 | // then
41 | assertThat(actualJson).matches(
42 | new JazonMap()
43 | .with("first", (String s) -> s.matches("bl.*"))
44 | );
45 | }
46 |
47 | /**
48 | * This is a failing test example.
49 | */
50 | @Test
51 | void testPredicatedWithDeeplyNestedException() {
52 | // given
53 | String actualJson = "{" +
54 | " \"first\": 55" +
55 | "}";
56 |
57 | // then
58 | assertThat(actualJson).matches(
59 | new JazonMap()
60 | .with("first", this::complexOperation)
61 | );
62 | }
63 |
64 | private boolean complexOperation(Integer number) {
65 | return failingOperation(number + 10);
66 | }
67 |
68 | private boolean failingOperation(int number) {
69 | throw new RuntimeException("an intentional exception");
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/examples/src/test/java/com/zendesk/jazon/junit/ExamplesWithGuavaTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.function.Predicate;
9 |
10 | import static com.zendesk.jazon.junit.JazonJunitAdapter.assertThat;
11 | import static java.util.Arrays.asList;
12 |
13 | class ExamplesWithGuavaTest {
14 |
15 | /**
16 | * This is a failing test example.
17 | */
18 | @Test
19 | void simpleTest() {
20 | // given
21 | String actualJson = "{\"value\": 123}";
22 | Map expectedJsonAsMap = ImmutableMap.builder()
23 | .put("value", 50)
24 | .build();
25 |
26 | // then
27 | assertThat(actualJson).matches(expectedJsonAsMap);
28 | }
29 |
30 | /**
31 | * This is a failing test example.
32 | */
33 | @Test
34 | void testWithNestedArray() {
35 | // given
36 | String actualJson = "{" +
37 | "\"value\": 50," +
38 | "\"tags\": [\"blue\", \"black\", \"red\"]" +
39 | "}";
40 |
41 | // then
42 | assertThat(actualJson).matches(
43 | deal(50, asList("blue", "pink", "red"))
44 | );
45 | }
46 |
47 | /**
48 | * This is a failing test example.
49 | */
50 | @Test
51 | void testWithRootArray() {
52 | // given
53 | String actualJson = "[\"blue\", \"black\", \"red\"]";
54 |
55 | // then
56 | assertThat(actualJson).matches(asList("blue", "pink", "red"));
57 | }
58 |
59 | /**
60 | * This is a passing test example.
61 | */
62 | @Test
63 | void testRegex() {
64 | // given
65 | String actualJson = "[\"blue\", \"black\", \"red\"]";
66 |
67 | // then
68 | assertThat(actualJson).matches(
69 | asList(
70 | regex("bl.*"),
71 | regex("bl.*"),
72 | regex("r.*")
73 | )
74 | );
75 | }
76 |
77 | private Predicate regex(String reg) {
78 | return s -> s.matches(reg);
79 | }
80 |
81 | private static Map deal(int value, List tags) {
82 | return ImmutableMap.builder()
83 | .put("value", value)
84 | .put("tags", tags)
85 | .build();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/examples/src/test/java/com/zendesk/jazon/junit/ReadmeExamplesTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.HashSet;
6 | import java.util.Objects;
7 | import java.util.Set;
8 | import java.util.function.Predicate;
9 |
10 | import static com.zendesk.jazon.junit.JazonJunitAdapter.assertThat;
11 | import static java.util.Arrays.asList;
12 |
13 | class ReadmeExamplesTest {
14 | private static final Predicate ANY_ID = (id) -> id >= 0;
15 | private static final Predicate ANY_ISO_DATETIME =
16 | datetime -> datetime.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
17 |
18 | @Test
19 | void simpleAssertionPasses() {
20 | // when
21 | String response = "{\"firstname\": \"Steve\", \"lastname\": \"Jobs\"}";
22 |
23 | // then
24 | assertThat(response).matches(
25 | new JazonMap()
26 | .with("firstname", "Steve")
27 | .with("lastname", "Jobs")
28 | );
29 | }
30 |
31 | @Test
32 | void unorderedArrayAssertionPasses() {
33 | // when
34 | String response = "{" +
35 | " \"id\": 95478,\n" +
36 | " \"name\": \"Coca Cola\",\n" +
37 | " \"tags\": [\"sprite\", \"pepsi\", \"7up\", \"fanta\", \"dr pepper\"]\n" +
38 | "}";
39 |
40 | // then
41 | assertThat(response).matches(
42 | new JazonMap()
43 | .with("id", 95478)
44 | .with("name", "Coca Cola")
45 | .with("tags", set("pepsi", "dr pepper", "sprite", "fanta", "7up"))
46 | );
47 | }
48 |
49 | @Test
50 | void customAssertions() {
51 | // when
52 | String response = "{\n" +
53 | " \"id\": 95478,\n" +
54 | " \"name\": \"Coca Cola\",\n" +
55 | " \"value\": \"133.30\",\n" +
56 | " \"updated_at\": \"1990-06-19T12:19:10Z\"\n" +
57 | "}";
58 |
59 | // then
60 | assertThat(response).matches(
61 | new JazonMap()
62 | .with("id", (Integer id) -> id >= 0)
63 | .with("name", "Coca Cola")
64 | .with("value", regex("\\d+\\.\\d\\d"))
65 | .with("updated_at", Objects::nonNull)
66 | );
67 | }
68 |
69 | @Test
70 | void utilsExtraction() {
71 | // when
72 | String response = "{\n" +
73 | " \"id\": 95478,\n" +
74 | " \"name\": \"Coca Cola\",\n" +
75 | " \"value\": \"133.30\",\n" +
76 | " \"updated_at\": \"1990-06-19T12:19:10Z\"\n" +
77 | "}";
78 |
79 | // then
80 | assertThat(response).matches(
81 | new JazonMap()
82 | .with("id", ANY_ID)
83 | .with("name", "Coca Cola")
84 | .with("value", "133.30")
85 | .with("updated_at", ANY_ISO_DATETIME)
86 | );
87 | }
88 |
89 | @Test
90 | void utilsExtractionToDomainObjects() {
91 | // when
92 | String response = "{\n" +
93 | " \"id\": 95478,\n" +
94 | " \"name\": \"Coca Cola\",\n" +
95 | " \"value\": \"10.00\",\n" +
96 | " \"updated_at\": \"1990-06-19T12:19:10Z\"\n" +
97 | "}";
98 |
99 | // then
100 | assertThat(response).matches(deal("Coca Cola", "10.00"));
101 | assertThat(response).matches(
102 | asList(
103 | deal("Coca Cola", "10.00"),
104 | deal("Pepsi", "9.00"),
105 | deal("Fanta", "10.00"),
106 | deal("Sprite", "10.00"),
107 | deal("Dr Pepper", "12.00")
108 | )
109 | );
110 | }
111 |
112 | private JazonMap deal(String name, String value) {
113 | return new JazonMap()
114 | .with("id", ANY_ID)
115 | .with("name", name)
116 | .with("value", value)
117 | .with("updated_at", ANY_ISO_DATETIME);
118 | }
119 |
120 | private Predicate regex(String regex) {
121 | return val -> val.matches(regex);
122 | }
123 |
124 | private Set set(Object... elements) {
125 | HashSet result = new HashSet<>(elements.length);
126 | result.addAll(asList(elements));
127 | return result;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | version = 0.4.1
2 |
--------------------------------------------------------------------------------
/gradle/publishing.gradle:
--------------------------------------------------------------------------------
1 | publishing {
2 | publications {
3 | maven(MavenPublication) {
4 | from components.java
5 | artifact sourceJar
6 | artifact javadocJar
7 |
8 | pom {
9 | name = project.ext.name
10 | packaging = 'jar'
11 | description = project.ext.description
12 | url = 'https://github.com/zendesk/jazon'
13 |
14 | scm {
15 | connection = 'scm:git:git://github.com/zendesk/jazon.git'
16 | developerConnection = 'scm:git:ssh://github.com:zendesk/jazon.git'
17 | url = 'http://github.com/zendesk/jazon/tree/master'
18 | }
19 |
20 | licenses {
21 | license {
22 | name = 'The Apache License, Version 2.0'
23 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
24 | }
25 | }
26 |
27 | developers {
28 | developer {
29 | id = 'pawel'
30 | name = 'Paweł Mikołajczyk'
31 | email = 'pmikolajczyk@zendesk.com'
32 | organization = 'Zendesk'
33 | organizationUrl = 'https://zendesk.com'
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zendesk/jazon/68d922d288ea6697e3de2bc65abca36449092efa/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-5.2.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS='"-Xmx64m"'
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS="-Xmx64m"
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/jazon-core/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | name = 'Jazon'
3 | description = 'A library for test assertions on JSON payloads.'
4 | }
5 |
6 | apply from: '../gradle/publishing.gradle'
7 |
8 | dependencies {
9 | compile group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
10 |
11 | compileOnly 'org.projectlombok:lombok:1.18.12'
12 | annotationProcessor 'org.projectlombok:lombok:1.18.12'
13 |
14 | testCompile group: 'org.spockframework', name: 'spock-core', version: '1.2-groovy-2.4'
15 | }
16 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/MatchResult.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon;
2 |
3 | import com.zendesk.jazon.mismatch.MismatchWithPath;
4 |
5 | import java.util.Optional;
6 | import java.util.function.Supplier;
7 |
8 | import static java.util.Optional.empty;
9 | import static java.util.Optional.of;
10 |
11 | public class MatchResult {
12 | private final Optional mismatch;
13 |
14 | public static MatchResult success() {
15 | return new MatchResult(empty());
16 | }
17 |
18 | public static MatchResult failure(MismatchWithPath mismatch) {
19 | return new MatchResult(of(mismatch));
20 | }
21 |
22 | private MatchResult(Optional mismatch) {
23 | this.mismatch = mismatch;
24 | }
25 |
26 | public boolean ok() {
27 | return !mismatch.isPresent();
28 | }
29 |
30 | public String message() {
31 | return mismatch
32 | .map(MismatchWithPath::message)
33 | .orElseThrow(cannotGetMessageException());
34 | }
35 |
36 | public MismatchWithPath mismatch() {
37 | return mismatch
38 | .orElseThrow(cannotGetMismatchException());
39 | }
40 |
41 | private Supplier cannotGetMessageException() {
42 | return () -> new IllegalStateException("MatchResult is OK. There is no Mismatch. You cannot get the message.");
43 | }
44 |
45 | private Supplier cannotGetMismatchException() {
46 | return () -> new IllegalStateException("MatchResult is OK. There is no Mismatch.");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/Matcher.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 | import com.zendesk.jazon.actual.factory.ActualFactory;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.expectation.translator.TranslatorFacade;
7 |
8 | public class Matcher {
9 | private final TranslatorFacade translator;
10 | private final ActualFactory actualFactory;
11 | private JsonExpectation expectation;
12 | private Actual actual;
13 |
14 | Matcher(TranslatorFacade translator, ActualFactory actualFactory) {
15 | this.translator = translator;
16 | this.actualFactory = actualFactory;
17 | }
18 |
19 | public MatchResult match() {
20 | return actual.accept(expectation, "$");
21 | }
22 |
23 | public Matcher expected(Object expectation) {
24 | this.expectation = translator.expectation(expectation);
25 | return this;
26 | }
27 |
28 | public Matcher actual(String actualJsonString) {
29 | this.actual = actualFactory.actual(actualJsonString);
30 | return this;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/MatcherFactory.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon;
2 |
3 | import com.zendesk.jazon.actual.factory.ActualFactory;
4 | import com.zendesk.jazon.expectation.translator.TranslatorFacade;
5 |
6 | public class MatcherFactory {
7 | private final TranslatorFacade translator;
8 | private final ActualFactory actualFactory;
9 |
10 | public MatcherFactory(TranslatorFacade translator, ActualFactory actualFactory) {
11 | this.translator = translator;
12 | this.actualFactory = actualFactory;
13 | }
14 |
15 | public Matcher matcher() {
16 | return new Matcher(translator, actualFactory);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/Actual.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 |
6 | public interface Actual {
7 | MatchResult accept(JsonExpectation expectation, String path);
8 | }
9 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonArray.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import lombok.EqualsAndHashCode;
6 |
7 | import java.util.List;
8 | import java.util.Objects;
9 |
10 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
11 | import static java.util.Collections.unmodifiableList;
12 | import static java.util.stream.Collectors.toList;
13 |
14 | @EqualsAndHashCode
15 | public class ActualJsonArray implements Actual {
16 | private final List list;
17 |
18 | public ActualJsonArray(List list) {
19 | this.list = checkNotNull(list);
20 | }
21 |
22 | public List list() {
23 | return unmodifiableList(list);
24 | }
25 |
26 | @Override
27 | public MatchResult accept(JsonExpectation expectation, String path) {
28 | return expectation.match(this, path);
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "[" + String.join(", ", strings()) + "]";
34 | }
35 |
36 | private List strings() {
37 | return list.stream()
38 | .map(Objects::toString)
39 | .collect(toList());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonBoolean.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import lombok.EqualsAndHashCode;
6 |
7 | @EqualsAndHashCode
8 | public class ActualJsonBoolean implements Actual {
9 | private final boolean value;
10 |
11 | public ActualJsonBoolean(boolean value) {
12 | this.value = value;
13 | }
14 |
15 | public boolean value() {
16 | return value;
17 | }
18 |
19 | @Override
20 | public MatchResult accept(JsonExpectation expectation, String path) {
21 | return expectation.match(this, path);
22 | }
23 |
24 | @Override
25 | public String toString() {
26 | return value ? "true" : "false";
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonNull.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 |
6 | public enum ActualJsonNull implements Actual {
7 | INSTANCE;
8 |
9 | @Override
10 | public MatchResult accept(JsonExpectation expectation, String path) {
11 | return expectation.match(this, path);
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "null";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonNumber.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import lombok.EqualsAndHashCode;
6 |
7 | import java.math.BigDecimal;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | @EqualsAndHashCode
12 | public class ActualJsonNumber implements Actual {
13 | private final Number number;
14 |
15 | public ActualJsonNumber(Number number) {
16 | checkPreconditions(number);
17 | this.number = sanitized(number);
18 | }
19 |
20 | public Number number() {
21 | return number;
22 | }
23 |
24 | @Override
25 | public MatchResult accept(JsonExpectation expectation, String path) {
26 | return expectation.match(this, path);
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return number.toString();
32 | }
33 |
34 | private static void checkPreconditions(Number number) {
35 | checkNotNull(number);
36 | checkSupportedType(number);
37 | }
38 |
39 | private static void checkSupportedType(Number number) {
40 | if (isOfSupportedType(number)) {
41 | return;
42 | }
43 | throw new IllegalArgumentException(
44 | String.format(
45 | "Given Number must be either Integer, Long, BigDecimal, Float or Double. Found: %s (%s)",
46 | number,
47 | number.getClass()
48 | )
49 | );
50 | }
51 |
52 | private static boolean isOfSupportedType(Number number) {
53 | return number instanceof Integer ||
54 | number instanceof Long ||
55 | number instanceof BigDecimal ||
56 | number instanceof Float ||
57 | number instanceof Double;
58 | }
59 |
60 | private Number sanitized(Number number) {
61 | if (number instanceof Long
62 | && number.longValue() <= Integer.MAX_VALUE
63 | && number.longValue() >= Integer.MIN_VALUE) {
64 | return number.intValue();
65 | }
66 | return number;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonObject.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import lombok.EqualsAndHashCode;
6 |
7 | import java.util.Map;
8 | import java.util.Optional;
9 | import java.util.Set;
10 | import java.util.stream.Collectors;
11 |
12 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
13 | import static java.util.Collections.unmodifiableMap;
14 |
15 | @EqualsAndHashCode
16 | public class ActualJsonObject implements Actual {
17 | private final Map map;
18 |
19 | public ActualJsonObject(Map map) {
20 | this.map = checkNotNull(map);
21 | }
22 |
23 | public Optional actualField(String fieldName) {
24 | return Optional.ofNullable(map.get(fieldName));
25 | }
26 |
27 | public Map map() {
28 | return unmodifiableMap(map);
29 | }
30 |
31 | public Set keys() {
32 | return map.keySet();
33 | }
34 |
35 | @Override
36 | public MatchResult accept(JsonExpectation expectation, String path) {
37 | return expectation.match(this, path);
38 | }
39 |
40 | public int size() {
41 | return map.size();
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | if (map.isEmpty()) {
47 | return "{}";
48 | }
49 | return "{" + fields() + "}";
50 | }
51 |
52 | private String fields() {
53 | return map.entrySet().stream()
54 | .map(e -> String.format("\"%s\": %s", e.getKey(), e.getValue()))
55 | .collect(Collectors.joining(", "));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/ActualJsonString.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import lombok.EqualsAndHashCode;
6 |
7 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
8 |
9 | @EqualsAndHashCode
10 | public class ActualJsonString implements Actual {
11 | private final String string;
12 |
13 | public ActualJsonString(String string) {
14 | this.string = checkNotNull(string);
15 | }
16 |
17 | public String string() {
18 | return string;
19 | }
20 |
21 | @Override
22 | public MatchResult accept(JsonExpectation expectation, String path) {
23 | return expectation.match(this, path);
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return String.format("\"%s\"", string);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/factory/ActualFactory.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual.factory;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 |
5 | public interface ActualFactory {
6 | Actual actual(T input);
7 | }
8 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/actual/factory/GsonActualFactory.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual.factory;
2 |
3 | import com.google.gson.*;
4 | import com.zendesk.jazon.actual.Actual;
5 | import com.zendesk.jazon.actual.ActualJsonArray;
6 | import com.zendesk.jazon.actual.ActualJsonBoolean;
7 | import com.zendesk.jazon.actual.ActualJsonNull;
8 | import com.zendesk.jazon.actual.ActualJsonNumber;
9 | import com.zendesk.jazon.actual.ActualJsonObject;
10 | import com.zendesk.jazon.actual.ActualJsonString;
11 | import com.zendesk.jazon.actual.factory.ActualFactory;
12 |
13 | import java.math.BigDecimal;
14 | import java.util.LinkedHashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.stream.Stream;
18 | import java.util.stream.StreamSupport;
19 |
20 | import static java.util.stream.Collectors.toList;
21 | import static java.util.stream.Collectors.toMap;
22 |
23 | public class GsonActualFactory implements ActualFactory {
24 | private static final JsonParser JSON_PARSER = new JsonParser();
25 |
26 | @Override
27 | public Actual actual(String input) {
28 | return fromJsonElement(JSON_PARSER.parse(input));
29 | }
30 |
31 | private Actual fromJsonElement(JsonElement jsonElement) {
32 | if (jsonElement.isJsonObject()) {
33 | return actualJsonObject((JsonObject) jsonElement);
34 | } else if (jsonElement.isJsonArray()) {
35 | JsonArray jsonArray = (JsonArray) jsonElement;
36 | return actualJsonArray(jsonArray);
37 | } else if (jsonElement.isJsonNull()) {
38 | return ActualJsonNull.INSTANCE;
39 | } else if (jsonElement.isJsonPrimitive()) {
40 | return actualJsonPrimitive((JsonPrimitive) jsonElement);
41 | }
42 | throw new IllegalStateException("Invalid JsonElement - not Object, not Array, not Null, not Primitive");
43 | }
44 |
45 | private ActualJsonObject actualJsonObject(JsonObject jsonObject) {
46 | Map map = jsonObject.entrySet()
47 | .stream()
48 | .collect(
49 | toMap(
50 | Map.Entry::getKey,
51 | e -> this.fromJsonElement(e.getValue()),
52 | (a, b) -> a,
53 | LinkedHashMap::new
54 | )
55 | );
56 | return new ActualJsonObject(map);
57 | }
58 |
59 | private ActualJsonArray actualJsonArray(JsonArray jsonArray) {
60 | List elements = stream(jsonArray)
61 | .map(this::fromJsonElement)
62 | .collect(toList());
63 | return new ActualJsonArray(elements);
64 |
65 | }
66 |
67 | private Actual actualJsonPrimitive(JsonPrimitive jsonPrimitive) {
68 | if (jsonPrimitive.isBoolean()) {
69 | return new ActualJsonBoolean(jsonPrimitive.getAsBoolean());
70 | } else if (jsonPrimitive.isNumber()) {
71 | return new ActualJsonNumber(intOrLongOrBigDecimal(jsonPrimitive));
72 | } else if (jsonPrimitive.isString()) {
73 | return new ActualJsonString(jsonPrimitive.getAsString());
74 | }
75 | throw new IllegalStateException("Invalid JsonPrimitive - not Boolean, not Number, not String");
76 | }
77 |
78 | private Number intOrLongOrBigDecimal(JsonPrimitive jsonPrimitive) {
79 | String numberAsString = jsonPrimitive.getAsString();
80 | if (isInteger(numberAsString)) {
81 | long numberAsLong = Long.parseLong(numberAsString);
82 | if (numberAsLong <= Integer.MAX_VALUE && numberAsLong >= Integer.MIN_VALUE) {
83 | return (int) numberAsLong;
84 | }
85 | return numberAsLong;
86 | }
87 | return new BigDecimal(numberAsString);
88 | }
89 |
90 | private boolean isInteger(String numberAsString) {
91 | return !numberAsString.contains(".") &&
92 | !numberAsString.contains("e") &&
93 | !numberAsString.contains("E");
94 | }
95 |
96 | private Stream stream(JsonArray jsonArray) {
97 | return StreamSupport.stream(jsonArray.spliterator(), false);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/Expectations.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation;
2 |
3 | import com.zendesk.jazon.expectation.impl.AnyNumberOf;
4 |
5 | public class Expectations {
6 |
7 | public static AnyNumberOf anyNumberOf(Object element) {
8 | return new AnyNumberOf(element);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/JsonExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 |
6 | public interface JsonExpectation {
7 | MatchResult match(ActualJsonNumber actualNumber, String path);
8 | MatchResult match(ActualJsonObject actualObject, String path);
9 | MatchResult match(ActualJsonString actualString, String path);
10 | MatchResult match(ActualJsonNull actualNull, String path);
11 | MatchResult match(ActualJsonArray actualArray, String path);
12 | MatchResult match(ActualJsonBoolean actualBoolean, String path);
13 | }
14 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/AnyNumberOf.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import lombok.Getter;
4 | import lombok.RequiredArgsConstructor;
5 |
6 | @RequiredArgsConstructor
7 | public class AnyNumberOf {
8 | @Getter
9 | private final Object elementExpectation;
10 | }
11 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/AnyNumberOfExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.Actual;
5 | import com.zendesk.jazon.actual.ActualJsonArray;
6 | import com.zendesk.jazon.actual.ActualJsonBoolean;
7 | import com.zendesk.jazon.actual.ActualJsonNull;
8 | import com.zendesk.jazon.actual.ActualJsonNumber;
9 | import com.zendesk.jazon.actual.ActualJsonObject;
10 | import com.zendesk.jazon.actual.ActualJsonString;
11 | import com.zendesk.jazon.expectation.JsonExpectation;
12 | import com.zendesk.jazon.mismatch.MismatchWithPath;
13 | import com.zendesk.jazon.mismatch.impl.NullMismatch;
14 | import com.zendesk.jazon.mismatch.impl.TypeMismatch;
15 | import lombok.EqualsAndHashCode;
16 | import lombok.RequiredArgsConstructor;
17 |
18 | import java.util.ListIterator;
19 |
20 | import static com.zendesk.jazon.MatchResult.failure;
21 | import static com.zendesk.jazon.MatchResult.success;
22 |
23 | /**
24 | * Previously known as {@code ArrayEachElementExpectation}
25 | */
26 | @RequiredArgsConstructor
27 | @EqualsAndHashCode
28 | public class AnyNumberOfExpectation implements JsonExpectation {
29 | private final JsonExpectation expectationForEachElement;
30 |
31 | @Override
32 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
33 | return failure(typeMismatch(ActualJsonNumber.class, path));
34 | }
35 |
36 | @Override
37 | public MatchResult match(ActualJsonObject actualObject, String path) {
38 | return failure(typeMismatch(ActualJsonObject.class, path));
39 | }
40 |
41 | @Override
42 | public MatchResult match(ActualJsonString actualString, String path) {
43 | return failure(typeMismatch(ActualJsonString.class, path));
44 | }
45 |
46 | @Override
47 | public MatchResult match(ActualJsonNull actualNull, String path) {
48 | return failure(
49 | new NullMismatch<>(this)
50 | .at(path)
51 | );
52 | }
53 |
54 | @Override
55 | public MatchResult match(ActualJsonArray actualArray, String path) {
56 | ListIterator actualValues = actualArray.list().listIterator();
57 | while (actualValues.hasNext()) {
58 | Actual actualValue = actualValues.next();
59 | MatchResult matchResult = actualValue.accept(expectationForEachElement, path + "." + actualValues.previousIndex());
60 | if (!matchResult.ok()) {
61 | return matchResult;
62 | }
63 | }
64 | return success();
65 | }
66 |
67 | @Override
68 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
69 | return failure(typeMismatch(ActualJsonBoolean.class, path));
70 | }
71 |
72 | private MismatchWithPath typeMismatch(Class extends Actual> actualType, String path) {
73 | return new TypeMismatch(ActualJsonArray.class, actualType)
74 | .at(path);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/NullExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.mismatch.MismatchWithPath;
7 | import com.zendesk.jazon.mismatch.impl.NotNullMismatch;
8 | import lombok.EqualsAndHashCode;
9 |
10 | import static com.zendesk.jazon.MatchResult.failure;
11 | import static com.zendesk.jazon.MatchResult.success;
12 |
13 | @EqualsAndHashCode
14 | public class NullExpectation implements JsonExpectation {
15 | @Override
16 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
17 | return failure(notNullMismatch(actualNumber, path));
18 | }
19 |
20 | @Override
21 | public MatchResult match(ActualJsonObject actualObject, String path) {
22 | return failure(notNullMismatch(actualObject, path));
23 | }
24 |
25 | @Override
26 | public MatchResult match(ActualJsonString actualString, String path) {
27 | return failure(notNullMismatch(actualString, path));
28 | }
29 |
30 | @Override
31 | public MatchResult match(ActualJsonNull actualNull, String path) {
32 | return success();
33 | }
34 |
35 | @Override
36 | public MatchResult match(ActualJsonArray actualArray, String path) {
37 | return failure(notNullMismatch(actualArray, path));
38 | }
39 |
40 | @Override
41 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
42 | return failure(notNullMismatch(actualBoolean, path));
43 | }
44 |
45 | private MismatchWithPath notNullMismatch(Actual actual, String path) {
46 | return new NotNullMismatch(actual)
47 | .at(path);
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "null";
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/ObjectExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.mismatch.*;
7 | import com.zendesk.jazon.mismatch.impl.NoFieldMismatch;
8 | import com.zendesk.jazon.mismatch.impl.NullMismatch;
9 | import com.zendesk.jazon.mismatch.impl.TypeMismatch;
10 | import com.zendesk.jazon.mismatch.impl.UnexpectedFieldMismatch;
11 | import lombok.AllArgsConstructor;
12 | import lombok.EqualsAndHashCode;
13 |
14 | import java.util.HashSet;
15 | import java.util.Map;
16 | import java.util.Optional;
17 | import java.util.Set;
18 | import java.util.stream.Collectors;
19 |
20 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
21 | import static com.zendesk.jazon.MatchResult.failure;
22 |
23 | @EqualsAndHashCode
24 | public class ObjectExpectation implements JsonExpectation {
25 | private final Map expectationMap;
26 |
27 | public ObjectExpectation(Map expectationMap) {
28 | this.expectationMap = checkNotNull(expectationMap);
29 | }
30 |
31 | @Override
32 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
33 | return failure(typeMismatch(ActualJsonNumber.class, path));
34 | }
35 |
36 | @Override
37 | public MatchResult match(ActualJsonObject actualObject, String path) {
38 | Optional mismatchFromExpectedFields = mismatchFromExpectedFields(actualObject, path);
39 | Optional mismatchFromUnexpected = mismatchFromUnexpected(actualObject, path);
40 | return firstOf(mismatchFromExpectedFields, mismatchFromUnexpected)
41 | .map(MatchResult::failure)
42 | .orElseGet(MatchResult::success);
43 | }
44 |
45 | @Override
46 | public MatchResult match(ActualJsonString actualString, String path) {
47 | return failure(typeMismatch(ActualJsonString.class, path));
48 | }
49 |
50 | @Override
51 | public MatchResult match(ActualJsonNull actualNull, String path) {
52 | return failure(
53 | new NullMismatch<>(this)
54 | .at(path)
55 | );
56 | }
57 |
58 | @Override
59 | public MatchResult match(ActualJsonArray actualArray, String path) {
60 | return failure(typeMismatch(ActualJsonArray.class, path));
61 | }
62 |
63 | @Override
64 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
65 | return failure(typeMismatch(ActualJsonBoolean.class, path));
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return partialJsonObject(2);
71 | }
72 |
73 | private String partialJsonObject(int firstFieldsCount) {
74 | if (expectationMap.isEmpty()) {
75 | return "{}";
76 | }
77 | String firstFields = expectationMap.entrySet().stream()
78 | .limit(firstFieldsCount)
79 | .map(e -> String.format("\"%s\": %s", e.getKey(), e.getValue()))
80 | .collect(Collectors.joining(", "));
81 | String suffix = firstFieldsCount < expectationMap.size() ? ", ...}" : "}";
82 | return "{" + firstFields + suffix;
83 | }
84 |
85 | private Optional mismatchFromExpectedFields(ActualJsonObject actualObject, String path) {
86 | return new MismatchFactory(actualObject, path)
87 | .mismatchFromExpectedFields();
88 | }
89 |
90 | private Optional mismatchFromUnexpected(ActualJsonObject actualObject, String path) {
91 | return new MismatchFactory(actualObject, path)
92 | .mismatchFromUnexpected();
93 | }
94 |
95 | private static Optional firstOf(Optional first, Optional second) {
96 | if (first.isPresent()) {
97 | return first;
98 | }
99 | return second;
100 | }
101 |
102 | private MismatchWithPath typeMismatch(Class extends Actual> actualType, String path) {
103 | return new TypeMismatch(ActualJsonObject.class, actualType)
104 | .at(path);
105 | }
106 |
107 | @AllArgsConstructor
108 | private class MismatchFactory {
109 | private final ActualJsonObject actualObject;
110 | private final String path;
111 |
112 | Optional mismatchFromExpectedFields() {
113 | return expectationMap.entrySet()
114 | .stream()
115 | .map(e -> matchResult(e.getKey(), e.getValue()))
116 | .filter(matchResult -> !matchResult.ok())
117 | .map(MatchResult::mismatch)
118 | .findFirst();
119 | }
120 |
121 | private Optional mismatchFromUnexpected() {
122 | Set unexpectedFields = setsDifference(actualObject.keys(), expectationMap.keySet());
123 | return unexpectedFields.stream()
124 | .map(fieldName ->
125 | new UnexpectedFieldMismatch(fieldName)
126 | .at(path)
127 | )
128 | .findFirst();
129 | }
130 |
131 | private MatchResult matchResult(String fieldName, JsonExpectation expectation) {
132 | return actualObject.actualField(fieldName)
133 | .map(actual -> actual.accept(expectation, path + "." + fieldName))
134 | .orElseGet(() ->
135 | failure(
136 | new NoFieldMismatch(fieldName, expectation)
137 | .at(path)
138 | )
139 | );
140 | }
141 |
142 | private Set setsDifference(Set first, Set second) {
143 | HashSet result = new HashSet<>();
144 | for (String memberOfFirst : first) {
145 | if (!second.contains(memberOfFirst)) {
146 | result.add(memberOfFirst);
147 | }
148 | }
149 | return result;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/OrderedArrayExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.mismatch.*;
7 | import com.zendesk.jazon.mismatch.impl.ArrayLackingElementsMismatch;
8 | import com.zendesk.jazon.mismatch.impl.ArrayUnexpectedElementsMismatch;
9 | import com.zendesk.jazon.mismatch.impl.NullMismatch;
10 | import com.zendesk.jazon.mismatch.impl.TypeMismatch;
11 | import lombok.EqualsAndHashCode;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collection;
15 | import java.util.Iterator;
16 | import java.util.List;
17 | import java.util.stream.Collectors;
18 |
19 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
20 | import static com.zendesk.jazon.MatchResult.failure;
21 | import static com.zendesk.jazon.MatchResult.success;
22 |
23 | @EqualsAndHashCode
24 | public class OrderedArrayExpectation implements JsonExpectation {
25 | private final List expectationList;
26 |
27 | public OrderedArrayExpectation(List expectationList) {
28 | this.expectationList = checkNotNull(expectationList);
29 | }
30 |
31 | @Override
32 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
33 | return failure(typeMismatch(ActualJsonNumber.class, path));
34 | }
35 |
36 | @Override
37 | public MatchResult match(ActualJsonObject actualObject, String path) {
38 | return failure(typeMismatch(ActualJsonObject.class, path));
39 | }
40 |
41 | @Override
42 | public MatchResult match(ActualJsonString actualString, String path) {
43 | return failure(typeMismatch(ActualJsonString.class, path));
44 | }
45 |
46 | @Override
47 | public MatchResult match(ActualJsonNull actualNull, String path) {
48 | return failure(
49 | new NullMismatch<>(this)
50 | .at(path)
51 | );
52 | }
53 |
54 | @Override
55 | public MatchResult match(ActualJsonArray actualArray, String path) {
56 | int index = 0;
57 | Iterator expectationIterator = expectationList.iterator();
58 | Iterator actualIterator = actualArray.list().iterator();
59 |
60 | while (expectationIterator.hasNext() && actualIterator.hasNext()) {
61 | JsonExpectation expectation = expectationIterator.next();
62 | Actual actual = actualIterator.next();
63 | MatchResult matchResult = actual.accept(expectation, path + "." + index);
64 | if (!matchResult.ok()) {
65 | return matchResult;
66 | }
67 | index += 1;
68 | }
69 |
70 | if (expectationIterator.hasNext()) {
71 | List lackingElements = remainingItems(expectationIterator);
72 | return failure(
73 | new ArrayLackingElementsMismatch(lackingElements)
74 | .at(path)
75 | );
76 | }
77 |
78 | if (actualIterator.hasNext()) {
79 | List unexpectedElements = remainingItems(actualIterator);
80 | return failure(
81 | new ArrayUnexpectedElementsMismatch(unexpectedElements)
82 | .at(path)
83 | );
84 | }
85 |
86 | return success();
87 | }
88 |
89 | @Override
90 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
91 | return failure(typeMismatch(ActualJsonBoolean.class, path));
92 | }
93 |
94 | @Override
95 | public String toString() {
96 | return "[" + String.join(", ", strings(expectationList)) + "]";
97 | }
98 |
99 | private List remainingItems(Iterator iterator) {
100 | ArrayList result = new ArrayList<>();
101 | iterator.forEachRemaining(result::add);
102 | return result;
103 | }
104 |
105 | private MismatchWithPath typeMismatch(Class extends Actual> actualType, String path) {
106 | return new TypeMismatch(ActualJsonArray.class, actualType)
107 | .at(path);
108 | }
109 |
110 | private Collection strings(Collection objects) {
111 | return objects.stream()
112 | .map(Object::toString)
113 | .collect(Collectors.toList());
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/PredicateExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.Actual;
5 | import com.zendesk.jazon.actual.ActualJsonArray;
6 | import com.zendesk.jazon.actual.ActualJsonBoolean;
7 | import com.zendesk.jazon.actual.ActualJsonNull;
8 | import com.zendesk.jazon.actual.ActualJsonNumber;
9 | import com.zendesk.jazon.actual.ActualJsonObject;
10 | import com.zendesk.jazon.actual.ActualJsonString;
11 | import com.zendesk.jazon.expectation.JsonExpectation;
12 | import com.zendesk.jazon.mismatch.impl.PredicateExecutionFailedMismatch;
13 | import com.zendesk.jazon.mismatch.impl.PredicateMismatch;
14 | import lombok.EqualsAndHashCode;
15 | import lombok.ToString;
16 |
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.function.Predicate;
21 |
22 | import static com.zendesk.jazon.MatchResult.failure;
23 | import static com.zendesk.jazon.MatchResult.success;
24 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
25 | import static java.util.stream.Collectors.toList;
26 |
27 | @ToString
28 | @EqualsAndHashCode
29 | public class PredicateExpectation implements JsonExpectation {
30 | private final Predicate> predicate;
31 |
32 | public PredicateExpectation(Predicate> predicate) {
33 | this.predicate = checkNotNull(predicate);
34 | }
35 |
36 | @Override
37 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
38 | return matchUnwrapped(actualNumber, path);
39 | }
40 |
41 | @Override
42 | public MatchResult match(ActualJsonObject actualObject, String path) {
43 | return matchUnwrapped(actualObject, path);
44 | }
45 |
46 | @Override
47 | public MatchResult match(ActualJsonString actualString, String path) {
48 | return matchUnwrapped(actualString, path);
49 | }
50 |
51 | @Override
52 | public MatchResult match(ActualJsonNull actualNull, String path) {
53 | return matchUnwrapped(actualNull, path);
54 | }
55 |
56 | @Override
57 | public MatchResult match(ActualJsonArray actualArray, String path) {
58 | return matchUnwrapped(actualArray, path);
59 | }
60 |
61 | @Override
62 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
63 | return matchUnwrapped(actualBoolean, path);
64 | }
65 |
66 | private MatchResult matchUnwrapped(Actual actual, String path) {
67 | Predicate objectPredicate = (Predicate) predicate;
68 | try {
69 | return objectPredicate.test(unwrap(actual))
70 | ? success()
71 | : failure(PredicateMismatch.INSTANCE.at(path));
72 | } catch (Exception e) {
73 | return failure(new PredicateExecutionFailedMismatch(e).at(path));
74 | }
75 | }
76 |
77 | private Map unwrapObject(ActualJsonObject actualObject) {
78 | Map resultMap = new HashMap<>(actualObject.size());
79 | for (Map.Entry entry : actualObject.map().entrySet()) {
80 | resultMap.put(entry.getKey(), unwrap(entry.getValue()));
81 | }
82 | return resultMap;
83 | }
84 |
85 | private List unwrapArray(ActualJsonArray actualJsonArray) {
86 | return actualJsonArray.list().stream()
87 | .map(this::unwrap)
88 | .collect(toList());
89 | }
90 |
91 | private Object unwrap(Actual actual) {
92 | if (actual instanceof ActualJsonString) {
93 | return ((ActualJsonString) actual).string();
94 | } else if (actual instanceof ActualJsonNumber) {
95 | return ((ActualJsonNumber) actual).number();
96 | } else if (actual instanceof ActualJsonBoolean) {
97 | return ((ActualJsonBoolean) actual).value();
98 | } else if (actual instanceof ActualJsonNull) {
99 | return null;
100 | } else if (actual instanceof ActualJsonObject) {
101 | return unwrapObject((ActualJsonObject) actual);
102 | } else if (actual instanceof ActualJsonArray) {
103 | return unwrapArray((ActualJsonArray) actual);
104 | }
105 | throw new IllegalArgumentException("Not a valid Actual object: " + actual);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/PrimitiveValueExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.mismatch.MismatchWithPath;
7 | import com.zendesk.jazon.mismatch.impl.NullMismatch;
8 | import com.zendesk.jazon.mismatch.impl.PrimitiveValueMismatch;
9 | import com.zendesk.jazon.mismatch.impl.TypeMismatch;
10 | import lombok.EqualsAndHashCode;
11 |
12 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
13 | import static com.zendesk.jazon.MatchResult.failure;
14 | import static com.zendesk.jazon.MatchResult.success;
15 |
16 | @EqualsAndHashCode
17 | public class PrimitiveValueExpectation implements JsonExpectation {
18 | private final T expectedValue;
19 |
20 | public PrimitiveValueExpectation(T expectedValue) {
21 | this.expectedValue = checkNotNull(expectedValue);
22 | }
23 |
24 | @Override
25 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
26 | return matchPrimitive(actualNumber, path);
27 | }
28 |
29 | @Override
30 | public MatchResult match(ActualJsonObject actualObject, String path) {
31 | return failure(typeMismatch(ActualJsonObject.class, path));
32 | }
33 |
34 | @Override
35 | public MatchResult match(ActualJsonString actualString, String path) {
36 | return matchPrimitive(actualString, path);
37 | }
38 |
39 | @Override
40 | public MatchResult match(ActualJsonNull actualNull, String path) {
41 | return failure(
42 | new NullMismatch<>(this)
43 | .at(path)
44 | );
45 | }
46 |
47 | @Override
48 | public MatchResult match(ActualJsonArray actualArray, String path) {
49 | return failure(typeMismatch(ActualJsonArray.class, path));
50 | }
51 |
52 | @Override
53 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
54 | return matchPrimitive(actualBoolean, path);
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return expectedValue.toString();
60 | }
61 |
62 | private MatchResult matchPrimitive(ActualType actualValue, String path) {
63 | if (actualValue.getClass() != expectedType()) {
64 | return failure(typeMismatch(actualValue.getClass(), path));
65 | }
66 | if (expectedValue.equals(actualValue)) {
67 | return success();
68 | }
69 | return failure(
70 | new PrimitiveValueMismatch<>(expectedValue, actualValue)
71 | .at(path)
72 | );
73 | }
74 |
75 | private MismatchWithPath typeMismatch(Class extends Actual> actualType, String path) {
76 | return new TypeMismatch(expectedType(), actualType)
77 | .at(path);
78 | }
79 |
80 | private Class extends Actual> expectedType() {
81 | return expectedValue.getClass();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/impl/UnorderedArrayExpectation.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.impl;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.actual.*;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 | import com.zendesk.jazon.mismatch.*;
7 | import com.zendesk.jazon.mismatch.impl.ArrayLackingElementsMismatch;
8 | import com.zendesk.jazon.mismatch.impl.ArrayUnexpectedElementsMismatch;
9 | import com.zendesk.jazon.mismatch.impl.NullMismatch;
10 | import com.zendesk.jazon.mismatch.impl.TypeMismatch;
11 | import lombok.EqualsAndHashCode;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collection;
15 | import java.util.HashSet;
16 | import java.util.Set;
17 | import java.util.stream.Collectors;
18 |
19 | import static com.zendesk.jazon.MatchResult.failure;
20 | import static com.zendesk.jazon.MatchResult.success;
21 | import static java.util.Arrays.asList;
22 |
23 | /**
24 | * FIXME:
25 | * There are 2 problems with this implementation:
26 | * 1. `match(ActualJsonArray)` method has time complexity of O(n^2) which is high cost for large sets.
27 | * 2. Due to naive implementation of `match(ActualJsonArray)` method, expectation types that are not
28 | * exact-equality-matches are not supported (currently the only such example is UnorderedArrayExpectation but
29 | * soon expected CustomPredicateExpectation similarly will not be supported in UnorderedArrayExpectation).
30 | * {@code SUPPORTED_EXPECTATION_TYPES} defines which types of expectation classes are supported.
31 | */
32 | @EqualsAndHashCode
33 | public class UnorderedArrayExpectation implements JsonExpectation {
34 | private static final Set> SUPPORTED_EXPECTATION_TYPES = new HashSet<>(asList(
35 | PrimitiveValueExpectation.class,
36 | ObjectExpectation.class,
37 | OrderedArrayExpectation.class
38 | ));
39 | private final Set expectationSet;
40 |
41 | public UnorderedArrayExpectation(Set expectationSet) {
42 | expectationSet.forEach(this::verifyExpectationSupported);
43 | this.expectationSet = expectationSet;
44 | }
45 |
46 | @Override
47 | public MatchResult match(ActualJsonNumber actualNumber, String path) {
48 | return failure(typeMismatch(ActualJsonNumber.class, path));
49 | }
50 |
51 | @Override
52 | public MatchResult match(ActualJsonObject actualObject, String path) {
53 | return failure(typeMismatch(ActualJsonObject.class, path));
54 | }
55 |
56 | @Override
57 | public MatchResult match(ActualJsonString actualString, String path) {
58 | return failure(typeMismatch(ActualJsonString.class, path));
59 | }
60 |
61 | @Override
62 | public MatchResult match(ActualJsonNull actualNull, String path) {
63 | return failure(
64 | new NullMismatch<>(this)
65 | .at(path)
66 | );
67 | }
68 |
69 | @Override
70 | public MatchResult match(ActualJsonArray actualArray, String path) {
71 | Set stillExpected = new HashSet<>(expectationSet);
72 | ArrayList actualList = new ArrayList<>(actualArray.list());
73 |
74 | for (JsonExpectation expectation : expectationSet) {
75 | for (int actualIndex = 0; actualIndex < actualList.size(); actualIndex++) {
76 | Actual actual = actualList.get(actualIndex);
77 | MatchResult result = actual.accept(expectation, path + ".?");
78 | if (result.ok()) {
79 | actualList.remove(actual);
80 | stillExpected.remove(expectation);
81 | break;
82 | }
83 | }
84 | }
85 |
86 | if (!stillExpected.isEmpty()) {
87 | return failure(
88 | new ArrayLackingElementsMismatch(stillExpected)
89 | .at(path)
90 | );
91 | }
92 | if (!actualList.isEmpty()) {
93 | return failure(
94 | new ArrayUnexpectedElementsMismatch(actualList)
95 | .at(path)
96 | );
97 | }
98 | return success();
99 | }
100 |
101 | @Override
102 | public MatchResult match(ActualJsonBoolean actualBoolean, String path) {
103 | return failure(typeMismatch(ActualJsonBoolean.class, path));
104 | }
105 |
106 | @Override
107 | public String toString() {
108 | return "[" + String.join(", ", strings(expectationSet)) + "] (unordered)";
109 | }
110 |
111 | private MismatchWithPath typeMismatch(Class extends Actual> actualType, String path) {
112 | return new TypeMismatch(ActualJsonArray.class, actualType)
113 | .at(path);
114 | }
115 |
116 | private void verifyExpectationSupported(JsonExpectation expectation) {
117 | boolean isSupported = SUPPORTED_EXPECTATION_TYPES.contains(expectation.getClass());
118 | if (!isSupported) {
119 | throw new IllegalStateException(
120 | String.format(
121 | "%s is not supported in %s",
122 | expectation.getClass(),
123 | UnorderedArrayExpectation.class.toString()
124 | )
125 | );
126 | }
127 | }
128 |
129 | private Collection strings(Collection objects) {
130 | return objects.stream()
131 | .map(Object::toString)
132 | .collect(Collectors.toList());
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/translator/DefaultTranslators.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.actual.ActualJsonBoolean;
4 | import com.zendesk.jazon.actual.ActualJsonNumber;
5 | import com.zendesk.jazon.actual.ActualJsonString;
6 | import com.zendesk.jazon.expectation.JsonExpectation;
7 | import com.zendesk.jazon.expectation.impl.ObjectExpectation;
8 | import com.zendesk.jazon.expectation.impl.OrderedArrayExpectation;
9 | import com.zendesk.jazon.expectation.impl.PredicateExpectation;
10 | import com.zendesk.jazon.expectation.impl.PrimitiveValueExpectation;
11 | import com.zendesk.jazon.expectation.impl.UnorderedArrayExpectation;
12 |
13 | import java.util.LinkedHashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 | import java.util.Set;
17 | import java.util.function.Predicate;
18 | import java.util.stream.Stream;
19 |
20 | import static java.util.Arrays.asList;
21 | import static java.util.stream.Collectors.toList;
22 | import static java.util.stream.Collectors.toMap;
23 | import static java.util.stream.Collectors.toSet;
24 |
25 | public class DefaultTranslators {
26 | public static List> translators() {
27 | return asList(
28 | new TranslatorMapping<>(Map.class, new MapTranslator()),
29 | new TranslatorMapping<>(List.class, new ListTranslator()),
30 | new TranslatorMapping<>(Set.class, new SetTranslator()),
31 | new TranslatorMapping<>(Predicate.class, new PredicateTranslator()),
32 | new TranslatorMapping<>(Number.class, new NumberTranslator()),
33 | new TranslatorMapping<>(String.class, new StringTranslator()),
34 | new TranslatorMapping<>(Boolean.class, new BooleanTranslator())
35 | );
36 | }
37 |
38 | @SuppressWarnings({"rawtypes", "unchecked"})
39 | private static class MapTranslator implements Translator {
40 | @Override
41 | public JsonExpectation jsonExpectation(Map object, TranslatorFacade translator) {
42 | Map objectsMap = (Map) object;
43 | LinkedHashMap expectationsMap = objectsMap.entrySet()
44 | .stream()
45 | .collect(
46 | toMap(
47 | e -> e.getKey().toString(),
48 | e -> translator.expectation(e.getValue()),
49 | (a, b) -> a,
50 | () -> new LinkedHashMap<>(objectsMap.size())
51 | )
52 | );
53 | return new ObjectExpectation(expectationsMap);
54 |
55 | }
56 | }
57 |
58 | @SuppressWarnings({"rawtypes", "unchecked"})
59 | private static class ListTranslator implements Translator {
60 | @Override
61 | public JsonExpectation jsonExpectation(List objectsList, TranslatorFacade translator) {
62 | Stream stream = objectsList.stream()
63 | .map(translator::expectation);
64 | List expectations = stream.collect(toList());
65 | return new OrderedArrayExpectation(expectations);
66 | }
67 | }
68 |
69 | @SuppressWarnings({"rawtypes", "unchecked"})
70 | private static class SetTranslator implements Translator {
71 | @Override
72 | public JsonExpectation jsonExpectation(Set objectsSet, TranslatorFacade translator) {
73 | Stream stream = objectsSet.stream()
74 | .map(translator::expectation);
75 | Set expectations = stream.collect(toSet());
76 | return new UnorderedArrayExpectation(expectations);
77 | }
78 | }
79 |
80 | @SuppressWarnings("rawtypes")
81 | private static class PredicateTranslator implements Translator {
82 | @Override
83 | public JsonExpectation jsonExpectation(Predicate predicate, TranslatorFacade translator) {
84 | return new PredicateExpectation(predicate);
85 | }
86 | }
87 |
88 | private static class NumberTranslator implements Translator {
89 | @Override
90 | public JsonExpectation jsonExpectation(Number number, TranslatorFacade translator) {
91 | return new PrimitiveValueExpectation<>(new ActualJsonNumber(number));
92 | }
93 | }
94 |
95 | private static class StringTranslator implements Translator {
96 | @Override
97 | public JsonExpectation jsonExpectation(String string, TranslatorFacade translator) {
98 | return new PrimitiveValueExpectation<>(new ActualJsonString(string));
99 | }
100 | }
101 |
102 | private static class BooleanTranslator implements Translator {
103 | @Override
104 | public JsonExpectation jsonExpectation(Boolean bool, TranslatorFacade translator) {
105 | return new PrimitiveValueExpectation<>(new ActualJsonBoolean(bool));
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/translator/JazonTypesTranslators.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.expectation.impl.AnyNumberOf;
4 | import com.zendesk.jazon.expectation.impl.AnyNumberOfExpectation;
5 | import com.zendesk.jazon.expectation.JsonExpectation;
6 |
7 | import java.util.List;
8 |
9 | import static java.util.Collections.singletonList;
10 |
11 | public class JazonTypesTranslators {
12 | public static List> translators() {
13 | return singletonList(
14 | new TranslatorMapping<>(AnyNumberOf.class, new AnyNumberOfTranslator())
15 | );
16 | }
17 |
18 | private static class AnyNumberOfTranslator implements Translator {
19 | @Override
20 | public JsonExpectation jsonExpectation(AnyNumberOf anyNumberOf, TranslatorFacade translator) {
21 | Object repeatedObject = anyNumberOf.getElementExpectation();
22 | JsonExpectation repeatedExpectation = translator.expectation(repeatedObject);
23 | return new AnyNumberOfExpectation(repeatedExpectation);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/translator/Translator.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 |
5 | public interface Translator {
6 | JsonExpectation jsonExpectation(T object, TranslatorFacade translator);
7 | }
8 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/translator/TranslatorFacade.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import com.zendesk.jazon.expectation.impl.NullExpectation;
5 | import lombok.AllArgsConstructor;
6 |
7 | import java.util.List;
8 |
9 | @AllArgsConstructor
10 | public class TranslatorFacade {
11 | private final List> translatorMappings;
12 |
13 | public JsonExpectation expectation(Object object) {
14 | if (object == null) {
15 | return new NullExpectation();
16 | }
17 | for (TranslatorMapping> translatorMapping : translatorMappings) {
18 | if (translatorMapping.supports(object)) {
19 | return translatorMapping.jsonExpectation(object, this);
20 | }
21 | }
22 | throw new IllegalArgumentException(String.format("Could not map this object to expectation: %s", object));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/expectation/translator/TranslatorMapping.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import lombok.AllArgsConstructor;
5 |
6 | @AllArgsConstructor
7 | public class TranslatorMapping {
8 | private final Class klass;
9 | private final Translator translator;
10 |
11 | boolean supports(Object object) {
12 | return klass.isInstance(object);
13 | }
14 |
15 | JsonExpectation jsonExpectation(Object object, TranslatorFacade translator) {
16 | return this.translator.jsonExpectation(cast(object), translator);
17 | }
18 |
19 | private T cast(Object object) {
20 | if (!supports(object)) {
21 | throw new IllegalArgumentException("Given object is not supported: " + object);
22 | }
23 | return klass.cast(object);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/Mismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch;
2 |
3 | public interface Mismatch {
4 | String message();
5 | }
6 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/MismatchWithPath.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.ToString;
5 |
6 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
7 |
8 | @ToString
9 | @EqualsAndHashCode
10 | public class MismatchWithPath {
11 | private final Mismatch internalMismatch;
12 | private final String path;
13 |
14 | public MismatchWithPath(Mismatch internalMismatch, String path) {
15 | this.internalMismatch = checkNotNull(internalMismatch);
16 | this.path = checkNotNull(path);
17 | }
18 |
19 | public Mismatch expectationMismatch() {
20 | return internalMismatch;
21 | }
22 |
23 | public String path() {
24 | return path;
25 | }
26 |
27 | public String message() {
28 | return "Mismatch at path: " + path + "\n" + internalMismatch.message();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/MismatchWithPathFactory.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch;
2 |
3 | public interface MismatchWithPathFactory {
4 | default MismatchWithPath at(String path) {
5 | return new MismatchWithPath((Mismatch) this, path);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/ArrayLackingElementsMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import java.util.Collection;
10 |
11 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
12 |
13 | @ToString
14 | @EqualsAndHashCode
15 | public class ArrayLackingElementsMismatch implements Mismatch, MismatchWithPathFactory {
16 | private final Collection lackingElements;
17 |
18 | public ArrayLackingElementsMismatch(Collection lackingElements) {
19 | this.lackingElements = checkNotNull(lackingElements);
20 | }
21 |
22 | @Override
23 | public String message() {
24 | return String.format("Array lacks the items: %s", lackingElements);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/ArrayUnexpectedElementsMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import java.util.List;
10 |
11 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
12 |
13 | @ToString
14 | @EqualsAndHashCode
15 | public class ArrayUnexpectedElementsMismatch implements Mismatch, MismatchWithPathFactory {
16 | private final List unexpectedElements;
17 |
18 | public ArrayUnexpectedElementsMismatch(List unexpectedElements) {
19 | this.unexpectedElements = checkNotNull(unexpectedElements);
20 | }
21 |
22 | @Override
23 | public String message() {
24 | return String.format("Array contains unexpected items: %s", unexpectedElements);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/NoFieldMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | @EqualsAndHashCode
12 | @ToString
13 | public class NoFieldMismatch implements Mismatch, MismatchWithPathFactory {
14 | private final String fieldName;
15 | private final JsonExpectation expectation;
16 |
17 | public NoFieldMismatch(String fieldName, JsonExpectation expectation) {
18 | this.fieldName = checkNotNull(fieldName);
19 | this.expectation = checkNotNull(expectation);
20 | }
21 |
22 | @Override
23 | public String message() {
24 | return String.format("Could not find expected field (\"%s\": %s)", fieldName, expectation);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/NotNullMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | @ToString
12 | @EqualsAndHashCode
13 | public class NotNullMismatch implements Mismatch, MismatchWithPathFactory {
14 | private final Actual actual;
15 |
16 | public NotNullMismatch(Actual actual) {
17 | this.actual = checkNotNull(actual);
18 | }
19 |
20 | @Override
21 | public String message() {
22 | return String.format("Expected null. Found: %s", actual);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/NullMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | @ToString
12 | @EqualsAndHashCode
13 | public class NullMismatch implements Mismatch, MismatchWithPathFactory {
14 | private final T expectedValue;
15 |
16 | public NullMismatch(T expectedValue) {
17 | this.expectedValue = checkNotNull(expectedValue);
18 | }
19 |
20 | @Override
21 | public String message() {
22 | return String.format("Found null. Expected: %s", expectedValue);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/PredicateExecutionFailedMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.mismatch.Mismatch;
4 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.ToString;
7 |
8 | import java.io.PrintWriter;
9 | import java.io.StringWriter;
10 |
11 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
12 |
13 | @EqualsAndHashCode
14 | @ToString
15 | public class PredicateExecutionFailedMismatch implements Mismatch, MismatchWithPathFactory {
16 | private final Throwable cause;
17 |
18 | public PredicateExecutionFailedMismatch(Throwable cause) {
19 | this.cause = checkNotNull(cause);
20 | }
21 |
22 | @Override
23 | public String message() {
24 | StringWriter stringWriter = new StringWriter();
25 |
26 | stringWriter.append("Exception occurred on predicate evaluation: \n\n");
27 | cause.printStackTrace(new PrintWriter(stringWriter));
28 |
29 | return stringWriter.toString();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/PredicateMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.mismatch.Mismatch;
4 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
5 | import lombok.ToString;
6 |
7 | @ToString
8 | public enum PredicateMismatch implements Mismatch, MismatchWithPathFactory {
9 | INSTANCE;
10 |
11 | @Override
12 | public String message() {
13 | return "Custom predicate does not match the value.";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/PrimitiveValueMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | @ToString
12 | @EqualsAndHashCode
13 | public class PrimitiveValueMismatch implements Mismatch, MismatchWithPathFactory {
14 | private final T expected;
15 | private final T actual;
16 |
17 | public PrimitiveValueMismatch(T expected, T actual) {
18 | this.expected = checkNotNull(expected);
19 | this.actual = checkNotNull(actual);
20 | }
21 |
22 | @Override
23 | public String message() {
24 | return String.format("Expected: %s\nActual: %s", expected, actual);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/TypeMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.actual.*;
4 | import com.zendesk.jazon.mismatch.Mismatch;
5 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.ToString;
8 |
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
13 | import static java.util.Collections.unmodifiableMap;
14 | import static java.util.Optional.ofNullable;
15 |
16 | @ToString
17 | @EqualsAndHashCode
18 | public class TypeMismatch implements Mismatch, MismatchWithPathFactory {
19 | private static final Map, String> JSON_TYPES = jsonTypes();
20 |
21 | private final Class extends Actual> expectedType;
22 |
23 | private final Class extends Actual> actualType;
24 | public TypeMismatch(Class extends Actual> expectedType, Class extends Actual> actualType) {
25 | this.expectedType = checkNotNull(expectedType);
26 | this.actualType = checkNotNull(actualType);
27 | }
28 |
29 | @Override
30 | public String message() {
31 | return String.format("Expected type: %s\nActual type: %s", string(expectedType), string(actualType));
32 | }
33 |
34 | private String string(Class extends Actual> jsonType) {
35 | return ofNullable(JSON_TYPES.get(jsonType))
36 | .orElseThrow(() -> new IllegalArgumentException("Invalid JSON type"));
37 | }
38 |
39 | private static Map, String> jsonTypes() {
40 | HashMap, String> jsonTypes = new HashMap<>();
41 | jsonTypes.put(ActualJsonObject.class, "Object");
42 | jsonTypes.put(ActualJsonArray.class, "Array");
43 | jsonTypes.put(ActualJsonString.class, "String");
44 | jsonTypes.put(ActualJsonNumber.class, "Number");
45 | jsonTypes.put(ActualJsonBoolean.class, "Boolean");
46 | jsonTypes.put(ActualJsonNull.class, "Null");
47 | return unmodifiableMap(jsonTypes);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/mismatch/impl/UnexpectedFieldMismatch.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch.impl;
2 |
3 | import com.zendesk.jazon.mismatch.Mismatch;
4 | import com.zendesk.jazon.mismatch.MismatchWithPathFactory;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.ToString;
7 |
8 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
9 |
10 | @ToString
11 | @EqualsAndHashCode
12 | public class UnexpectedFieldMismatch implements Mismatch, MismatchWithPathFactory {
13 | private final String fieldName;
14 |
15 | public UnexpectedFieldMismatch(String fieldName) {
16 | this.fieldName = checkNotNull(fieldName);
17 | }
18 |
19 | @Override
20 | public String message() {
21 | return String.format("Unexpected field \"%s\" in object.", fieldName);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/jazon-core/src/main/java/com/zendesk/jazon/util/Preconditions.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.util;
2 |
3 | import lombok.NoArgsConstructor;
4 |
5 | import static lombok.AccessLevel.PRIVATE;
6 |
7 | @NoArgsConstructor(access = PRIVATE)
8 | public final class Preconditions {
9 |
10 | public static T checkNotNull(T object) {
11 | if (object == null) {
12 | throw new NullPointerException();
13 | }
14 | return object;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/jazon-core/src/test/groovy/com/zendesk/jazon/MatcherSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 |
4 | import com.zendesk.jazon.actual.ActualJsonArray
5 | import com.zendesk.jazon.actual.ActualJsonBoolean
6 | import com.zendesk.jazon.actual.ActualJsonNull
7 | import com.zendesk.jazon.actual.ActualJsonNumber
8 | import com.zendesk.jazon.actual.ActualJsonObject
9 | import com.zendesk.jazon.actual.ActualJsonString
10 | import com.zendesk.jazon.actual.factory.GsonActualFactory
11 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
12 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
13 | import com.zendesk.jazon.expectation.JsonExpectation
14 | import com.zendesk.jazon.expectation.impl.NullExpectation
15 | import com.zendesk.jazon.expectation.impl.PrimitiveValueExpectation
16 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
17 | import com.zendesk.jazon.mismatch.impl.ArrayLackingElementsMismatch
18 | import com.zendesk.jazon.mismatch.impl.ArrayUnexpectedElementsMismatch
19 | import com.zendesk.jazon.mismatch.impl.NoFieldMismatch
20 | import com.zendesk.jazon.mismatch.impl.NotNullMismatch
21 | import com.zendesk.jazon.mismatch.impl.NullMismatch
22 | import com.zendesk.jazon.mismatch.impl.PredicateMismatch
23 | import com.zendesk.jazon.mismatch.impl.PrimitiveValueMismatch
24 | import com.zendesk.jazon.mismatch.impl.TypeMismatch
25 | import com.zendesk.jazon.mismatch.impl.UnexpectedFieldMismatch
26 | import spock.lang.Specification
27 | import spock.lang.Unroll
28 |
29 | import java.util.function.Predicate
30 |
31 | import static com.zendesk.jazon.expectation.Expectations.anyNumberOf
32 | import static groovy.json.JsonOutput.toJson
33 |
34 | class MatcherSpec extends Specification {
35 | private static TestActualFactory testActualFactory = new TestActualFactory()
36 | private static MatcherFactory matcherFactory = new MatcherFactory(
37 | new TranslatorFacade(DefaultTranslators.translators() + JazonTypesTranslators.translators()),
38 | new GsonActualFactory()
39 | )
40 |
41 | @Unroll
42 | def "primitive value mismatch (expected: #expected, actual: #actual)"() {
43 | when:
44 | def result = match([a: expected], [a: actual])
45 |
46 | then:
47 | !result.ok()
48 | result.mismatch().expectationMismatch() == primitiveValueMismatch(expected, actual)
49 | result.mismatch().path() == '$.a'
50 |
51 | where:
52 | expected | actual
53 | 123 | 10
54 | 123 | new BigDecimal("11.05")
55 | 123 | 12345l
56 | 130.1f | 10
57 | 130.1f | new BigDecimal("11.05")
58 | 130.1f | 12345l
59 | 1500.13d | 10
60 | 1500.13d | new BigDecimal("11.05")
61 | 1500.13d | 12345l
62 | new BigDecimal("11.05") | 10
63 | new BigDecimal("11.05") | new BigDecimal("11.11")
64 | new BigDecimal("11.05") | 12345l
65 | 12345l | 10
66 | 12345l | new BigDecimal("11.05")
67 | 12345l | 1234567l
68 | 'green' | 'red'
69 | true | false
70 | false | true
71 | }
72 |
73 | @Unroll
74 | def "primitive value mismatch for floating Actuals (expected: #expected, actual: #actual)"() {
75 | when:
76 | def result = match([a: expected], [a: actualFloating])
77 |
78 | then:
79 | !result.ok()
80 | result.mismatch().expectationMismatch() == primitiveValueMismatch(expected, actualDecimal)
81 | result.mismatch().path() == '$.a'
82 |
83 | where:
84 | expected | actualFloating | actualDecimal
85 | 123 | 130.1f | new BigDecimal('130.1')
86 | 123 | 1500.13d | new BigDecimal('1500.13')
87 | 130.1f | 133.3f | new BigDecimal('133.3')
88 | 130.1f | 1500.13d | new BigDecimal('1500.13')
89 | 1500.13d | 130.1f | new BigDecimal('130.1')
90 | 1500.13d | 1555.55d | new BigDecimal('1555.55')
91 | new BigDecimal("11.05") | 130.1f | new BigDecimal('130.1')
92 | new BigDecimal("11.05") | 1500.13d | new BigDecimal('1500.13')
93 | 12345l | 130.1f | new BigDecimal('130.1')
94 | 12345l | 1500.13d | new BigDecimal('1500.13')
95 | }
96 |
97 | def "simple primitive type mismatch"() {
98 | when:
99 | def result = match(expected, actual)
100 |
101 | then:
102 | !result.ok()
103 | result.mismatch().expectationMismatch() == new TypeMismatch(mismatchExpectedType, mismatchActualType)
104 | result.mismatch().path() == '$.a'
105 |
106 | where:
107 | expected | actual | mismatchExpectedType | mismatchActualType
108 | [a: 123] | [a: 'red'] | ActualJsonNumber.class | ActualJsonString.class
109 | [a: 123] | [a: [bb: 10]] | ActualJsonNumber.class | ActualJsonObject.class
110 | [a: 123] | [a: true] | ActualJsonNumber.class | ActualJsonBoolean.class
111 | [a: 123] | [a: [1, 2]] | ActualJsonNumber.class | ActualJsonArray.class
112 | [a: 'ww'] | [a: 88] | ActualJsonString.class | ActualJsonNumber.class
113 | [a: 'ww'] | [a: [bb: 10]] | ActualJsonString.class | ActualJsonObject.class
114 | [a: 'ww'] | [a: true] | ActualJsonString.class | ActualJsonBoolean.class
115 | [a: 'ww'] | [a: [1, 2]] | ActualJsonString.class | ActualJsonArray.class
116 | [a: [bb: 10]] | [a: 88] | ActualJsonObject.class | ActualJsonNumber.class
117 | [a: [bb: 10]] | [a: 'red'] | ActualJsonObject.class | ActualJsonString.class
118 | [a: [bb: 10]] | [a: true] | ActualJsonObject.class | ActualJsonBoolean.class
119 | [a: [bb: 10]] | [a: [1, 2]] | ActualJsonObject.class | ActualJsonArray.class
120 | [a: true] | [a: 'red'] | ActualJsonBoolean.class | ActualJsonString.class
121 | [a: true] | [a: 88] | ActualJsonBoolean.class | ActualJsonNumber.class
122 | [a: true] | [a: [bb: 10]] | ActualJsonBoolean.class | ActualJsonObject.class
123 | [a: true] | [a: [1, 2]] | ActualJsonBoolean.class | ActualJsonArray.class
124 | [a: [1, 2]] | [a: 123] | ActualJsonArray.class | ActualJsonNumber.class
125 | [a: [1, 2]] | [a: 'red'] | ActualJsonArray.class | ActualJsonString.class
126 | [a: [1, 2]] | [a: 88] | ActualJsonArray.class | ActualJsonNumber.class
127 | [a: [1, 2]] | [a: [bb: 10]] | ActualJsonArray.class | ActualJsonObject.class
128 | [a: [1, 2] as Set] | [a: 123] | ActualJsonArray.class | ActualJsonNumber.class
129 | [a: [1, 2] as Set] | [a: 'red'] | ActualJsonArray.class | ActualJsonString.class
130 | [a: [1, 2] as Set] | [a: 88] | ActualJsonArray.class | ActualJsonNumber.class
131 | [a: [1, 2] as Set] | [a: [bb: 10]] | ActualJsonArray.class | ActualJsonObject.class
132 | }
133 |
134 | def "matches numbers in object even if they have different types"() {
135 | when:
136 | def result = match([a: expected], [a: actual])
137 |
138 | then:
139 | result.ok()
140 |
141 | where:
142 | expected | actual
143 | 1 as int | 1 as long
144 | 1 as long | 1 as int
145 | }
146 |
147 | def "matches numbers in array even if they have different types"() {
148 | when:
149 | def result = match([a: [1, 2, expected]], [a: [1, 2, actual]])
150 |
151 | then:
152 | result.ok()
153 |
154 | where:
155 | expected | actual
156 | 3 as int | 3 as long
157 | 3 as long | 3 as int
158 | }
159 |
160 | @Unroll
161 | def "finds null instead of primitive value: #expected"() {
162 | when:
163 | def result = match([a: expected], [a: null])
164 |
165 | then:
166 | !result.ok()
167 | result.mismatch().expectationMismatch() == new NullMismatch(expectationInstance)
168 | result.mismatch().path() == '$.a'
169 |
170 | where:
171 | expected | expectationInstance
172 | 123 | primitive(123)
173 | 130.1f | primitive(130.1f)
174 | 1500.13d | primitive(1500.13d)
175 | new BigDecimal("11.05") | primitive(new BigDecimal("11.05"))
176 | 12345l | primitive(12345l)
177 | "sting" | primitive("sting")
178 | true | primitive(true)
179 | }
180 |
181 | @Unroll
182 | def "mismatch in object field (expected: #expectedFieldValue, actual: #actualFieldValue)"() {
183 | given:
184 | def expected = [
185 | a: 103,
186 | b: expectedFieldValue
187 | ]
188 | def actual = [
189 | a: 103,
190 | b: actualFieldValue,
191 | ]
192 |
193 | when:
194 | def result = match(expected, actual)
195 |
196 | then:
197 | !result.ok()
198 | result.mismatch().expectationMismatch() == foundMismatch
199 | result.mismatch().path() == mismatchPath
200 |
201 | where:
202 | expectedFieldValue | actualFieldValue || mismatchPath | foundMismatch
203 | 'vegetable' | 'meat' || '$.b' | primitiveValueMismatch('vegetable', 'meat')
204 | 'vegetable' | null || '$.b' | new NullMismatch<>(primitive('vegetable'))
205 | 'vegetable' | 150 || '$.b' | new TypeMismatch(ActualJsonString, ActualJsonNumber)
206 | 77 | 'rosemary' || '$.b' | new TypeMismatch(ActualJsonNumber, ActualJsonString)
207 | [] | 'car' || '$.b' | new TypeMismatch(ActualJsonArray, ActualJsonString)
208 | [20, 30] | [20, 77] || '$.b.1' | primitiveValueMismatch(30, 77)
209 | }
210 |
211 | def "catches lacking field in Object"() {
212 | given:
213 | def expected = [
214 | a: 103,
215 | b: 'some value',
216 | ]
217 |
218 | when:
219 | def result = match(expected, actual)
220 |
221 | then:
222 | !result.ok()
223 | result.mismatch().expectationMismatch() == new NoFieldMismatch(
224 | 'b',
225 | new PrimitiveValueExpectation<>(new ActualJsonString('some value'))
226 | )
227 | result.mismatch().path() == '$'
228 |
229 | where:
230 | actual << [
231 | [a: 103],
232 | [a: 103, c: 'car']
233 | ]
234 | }
235 |
236 | @Unroll
237 | def "catches unexpected field in Object: #unexpectedFieldValue"() {
238 | given:
239 | def expected = [
240 | a: 103,
241 | c: 'Chicago',
242 | ]
243 | def actual = [
244 | a: 103,
245 | b: unexpectedFieldValue,
246 | c: 'Chicago',
247 | ]
248 |
249 | when:
250 | def result = match(expected, actual)
251 |
252 | then:
253 | !result.ok()
254 | result.mismatch().expectationMismatch() == new UnexpectedFieldMismatch('b')
255 | result.mismatch().path() == '$'
256 |
257 | where:
258 | unexpectedFieldValue | unexpectedFieldType
259 | 'act of vandalism' | ActualJsonString
260 | 123 | ActualJsonNumber
261 | 1999l | ActualJsonNumber
262 | 20.14f | ActualJsonNumber
263 | 44.999d | ActualJsonNumber
264 | new BigDecimal("80.92") | ActualJsonNumber
265 | [a: 1, b: 'blue'] | ActualJsonObject
266 | null | ActualJsonNull
267 | [5, 4, 3] | ActualJsonArray
268 | true | ActualJsonBoolean
269 | false | ActualJsonBoolean
270 | }
271 |
272 | @Unroll
273 | def "object expectation - type mismatch for #actualType"() {
274 | given:
275 | def theObject = [
276 | id : 1,
277 | name : "Leo",
278 | nationality: "Argentinian"
279 | ]
280 | def expected = [a: theObject]
281 | def actual = [a: actualValue]
282 |
283 | when:
284 | def result = match(expected, actual)
285 |
286 | then:
287 | !result.ok()
288 | result.mismatch().expectationMismatch() == new TypeMismatch(ActualJsonObject, actualType)
289 | result.mismatch().path() == '$.a'
290 |
291 | where:
292 | actualValue | actualType
293 | true | ActualJsonBoolean
294 | 130 | ActualJsonNumber
295 | 'orange' | ActualJsonString
296 | [1, 2, 3] | ActualJsonArray
297 | }
298 |
299 | @Unroll
300 | def "ordered list expectation - exact element mismatch"() {
301 | when:
302 | def result = match([a: expected], [a: actual])
303 |
304 | then:
305 | !result.ok()
306 | result.mismatch().expectationMismatch() == elementMismatch
307 | result.mismatch().path() == '$.a.' + elementIndex
308 |
309 | where:
310 | expected | actual || elementIndex | elementMismatch
311 | [1, 2, 3] | [3, 2, 1] || 0 | primitiveValueMismatch(1, 3)
312 | [1, 2, 3] | [1, 7, 3] || 1 | primitiveValueMismatch(2, 7)
313 | [1, 2, true] | [1, 2, 3] || 2 | new TypeMismatch(ActualJsonBoolean, ActualJsonNumber)
314 | [1, 2, 3] | [1, 2, true] || 2 | new TypeMismatch(ActualJsonNumber, ActualJsonBoolean)
315 | [1, null, 3] | [1, 2, 3] || 1 | new NotNullMismatch(new ActualJsonNumber(2))
316 | [1, 2, 3] | [1, null, 3] || 1 | new NullMismatch<>(primitive(2))
317 | [1, 2, 3] | [1, 2, 4, 5] || 2 | primitiveValueMismatch(3, 4)
318 | }
319 |
320 | @Unroll
321 | def "ordered list expectation - lacking elements (expected: #expected, actual: #actual)"() {
322 | when:
323 | def result = match([a: expected], [a: actual])
324 |
325 | then:
326 | !result.ok()
327 | result.mismatch().expectationMismatch() == new ArrayLackingElementsMismatch(
328 | lackingElements.collect(this.&expectation)
329 | )
330 | result.mismatch().path() == '$.a'
331 |
332 | where:
333 | expected | actual || lackingElements
334 | [1, 2, 3] | [1, 2] || [3]
335 | [1, 2, 'lalala'] | [1, 2] || ['lalala']
336 | [1, 2, 'lalala', 5, 6, 7] | [1, 2] || ['lalala', 5, 6, 7]
337 | [1, 2, null, 5, 6, 7] | [1, 2, null, 5] || [6, 7]
338 | [1, null] | [1] || [null]
339 | [9] | [] || [9]
340 | [null] | [] || [null]
341 | [null, 'car', 17] | [] || [null, 'car', 17]
342 | }
343 |
344 | def "ordered list expectation - unexpected elements"() {
345 | when:
346 | def result = match([a: expected], [a: actual])
347 |
348 | then:
349 | !result.ok()
350 | result.mismatch().expectationMismatch() == new ArrayUnexpectedElementsMismatch(
351 | unexpectedElements.collect(testActualFactory.&actual)
352 | )
353 | result.mismatch().path() == '$.a'
354 |
355 | where:
356 | expected | actual || unexpectedElements
357 | [1, 2] | [1, 2, 3] || [3]
358 | [] | [1, 2, 3] || [1, 2, 3]
359 | ['carpet'] | ['carpet', 'fur'] || ['fur']
360 | [] | [null] || [null]
361 | [] | [null, null] || [null, null]
362 | [1, 2] | [1, 2, null] || [null]
363 | [true, 'bike'] | [true, 'bike', false] || [false]
364 | }
365 |
366 | @Unroll
367 | def "ordered list expectation - type mismatch for #actualType"() {
368 | given:
369 | def theArray = ['white bear', 'seal', 'penguin']
370 | def expected = [a: theArray]
371 | def actual = [a: actualValue]
372 |
373 | when:
374 | def result = match(expected, actual)
375 |
376 | then:
377 | !result.ok()
378 | result.mismatch().expectationMismatch() == new TypeMismatch(ActualJsonArray, actualType)
379 | result.mismatch().path() == '$.a'
380 |
381 | where:
382 | actualValue | actualType
383 | true | ActualJsonBoolean
384 | 130 | ActualJsonNumber
385 | 'orange' | ActualJsonString
386 | [a: 44] | ActualJsonObject
387 | }
388 |
389 | def "unordered list expectation: success"() {
390 | when:
391 | def result = match([a: expected], [a: actual])
392 |
393 | then:
394 | result.ok()
395 |
396 | where:
397 | expected | actual
398 | [1, 2, 3] as Set | [1, 3, 2]
399 | [1, 2, 3] as Set | [3, 1, 2]
400 | [1, 2, 'lalala'] as Set | ['lalala', 1, 2]
401 | [1, 2, 'lalala', 5, 6, 7] as Set | [6, 7, 2, 5, 'lalala', 1]
402 | }
403 |
404 | @Unroll
405 | def "unordered list expectation: lacking elements (actual: #actual)"() {
406 | when:
407 | def result = match([a: expected], [a: actual])
408 |
409 | then:
410 | !result.ok()
411 | result.mismatch().expectationMismatch() == new ArrayLackingElementsMismatch(
412 | lackingElements.collect(this.&expectation) as Set
413 | )
414 | result.mismatch().path() == '$.a'
415 |
416 | where:
417 | expected | actual || lackingElements
418 | [1, 2, 3] as Set | [1, 3] || [2]
419 | [1, 2, 3] as Set | [1, 3, 1] || [2]
420 | [1, 2, 3] as Set | [3, 1, 1, 88] || [2]
421 | [1, 2, 3] as Set | [3, 1, 3, 1] || [2]
422 | [1, 2, 'lalala'] as Set | ['lalala', 11, 2] || [1]
423 | [1, 2, 'lalala', 5, 6, 7] as Set | [6, 7, 2, 5, 'rob', 1] || ['lalala']
424 | [3, 4, 2, 1] as Set | [11, 2, 3, 55] || [1, 4]
425 | [3, 4, 2, 1] as Set | [1] || [2, 3, 4]
426 | [3, 4, 2, 1] as Set | [2] || [1, 3, 4]
427 | [3, 4, 2, 1] as Set | [3] || [1, 2, 4]
428 | [3, 4, 2, 1] as Set | [4] || [1, 2, 3]
429 | }
430 |
431 | def "unordered list expectation: unexpected elements"() {
432 | when:
433 | def result = match([a: expected], [a: actual])
434 |
435 | then:
436 | !result.ok()
437 | result.mismatch().expectationMismatch() == new ArrayUnexpectedElementsMismatch(
438 | unexpectedElements.collect(testActualFactory.&actual)
439 | )
440 | result.mismatch().path() == '$.a'
441 |
442 | where:
443 | expected | actual || unexpectedElements
444 | [1, 2, 3] as Set | [1, 3, 2, 8] || [8]
445 | [1, 2, 3] as Set | [1, 3, 2, 'sushi'] || ['sushi']
446 | [1, 2, 3] as Set | [1, 'sushi', 2, 3] || ['sushi']
447 | [1, 2, 3] as Set | [1, 3, 2, null] || [null]
448 | ['what', 'is', 'love'] as Set | ['love', 'is', 10, 'what'] || [10]
449 | }
450 |
451 | @Unroll
452 | def "unordered list expectation - type mismatch for #actualType"() {
453 | given:
454 | def theSet = ['white bear', 'seal', 'penguin'] as Set
455 | def expected = [a: theSet]
456 | def actual = [a: actualValue]
457 |
458 | when:
459 | def result = match(expected, actual)
460 |
461 | then:
462 | !result.ok()
463 | result.mismatch().expectationMismatch() == new TypeMismatch(ActualJsonArray, actualType)
464 | result.mismatch().path() == '$.a'
465 |
466 | where:
467 | actualValue | actualType
468 | true | ActualJsonBoolean
469 | 130 | ActualJsonNumber
470 | 'orange' | ActualJsonString
471 | [a: 44] | ActualJsonObject
472 | }
473 |
474 | def "unordered list expectation: fails for unsupported expectation types"() {
475 | given:
476 | def unsupportedExpectation = [1, 2, 3] as Set
477 | def unorderedArrayExpectationWrapping = ['fish', 'chips', unsupportedExpectation] as Set
478 |
479 | when:
480 | match([a: unorderedArrayExpectationWrapping], [a: unorderedArrayExpectationWrapping])
481 |
482 | then:
483 | thrown(IllegalStateException)
484 | }
485 |
486 | @Unroll
487 | def "array each element expectation: success"() {
488 | expect:
489 | match([a: anyNumberOf(expected)], [a: actual]).success()
490 |
491 | where:
492 | expected | actual
493 | '1' | []
494 | '1' | ['1']
495 | '1' | ['1', '1']
496 | true | [true]
497 | 2 | [2]
498 | [b: true, c: 1] | [[[b: true, c: 1]]]
499 | [3, 4, 5] | [[3, 4, 5]]
500 | { it -> it > 5 } as Predicate | [6, 7, 8]
501 | }
502 |
503 | @Unroll
504 | def "array each element expectation - element mismatch"() {
505 | when:
506 | def result = match([a: anyNumberOf(expected)], [a: actual])
507 |
508 | then:
509 | !result.ok()
510 | result.mismatch().expectationMismatch() == elementMismatch
511 | result.mismatch().path() == '$.a.' + path
512 |
513 | where:
514 | expected | actual || path | elementMismatch | _
515 | 1 | [1, 3, 1] || '1' | primitiveValueMismatch(1, 3) | _
516 | 1 | [1, 1, true] || '2' | new TypeMismatch(ActualJsonNumber, ActualJsonBoolean) | _
517 | true | [true, 1, true] || '1' | new TypeMismatch(ActualJsonBoolean, ActualJsonNumber) | _
518 | 1 | [1, null, 1] || '1' | new NullMismatch<>(expectation(1)) | _
519 | [b: true, c: 1] | [[b: true, c: 2]] || '0.c' | primitiveValueMismatch(1, 2) | _
520 | [3, 4, 5] | [[3, 4, false]] || '0.2' | new TypeMismatch(ActualJsonNumber, ActualJsonBoolean) | _
521 | ({ it -> it > 3 }
522 | as Predicate) | [4, 5, 2] || '2' | PredicateMismatch.INSTANCE | _
523 | }
524 |
525 | def "null expectation: fails for any present value"() {
526 | when:
527 | def result = match([a: null], [a: actual])
528 |
529 | then:
530 | !result.ok()
531 | result.mismatch().expectationMismatch() == new NotNullMismatch(testActualFactory.actual(actual))
532 | result.mismatch().path() == '$.a'
533 |
534 | where:
535 | actual << [
536 | 'something',
537 | 10,
538 | new BigDecimal("11.05"),
539 | 12345l,
540 | [x: 123],
541 | [1, 2, 3],
542 | true,
543 | false
544 | ]
545 | }
546 |
547 | def "null expectation: fails for any present float/double"() {
548 | when:
549 | def result = match([a: null], [a: actualFloating])
550 |
551 | then:
552 | !result.ok()
553 | result.mismatch().expectationMismatch() == new NotNullMismatch(testActualFactory.actual(actualDecimal))
554 | result.mismatch().path() == '$.a'
555 |
556 | where:
557 | actualFloating | actualDecimal
558 | 130.1f | new BigDecimal('130.1')
559 | 1555.55d | new BigDecimal('1555.55')
560 | }
561 |
562 | def "null expectation: succeeds for null"() {
563 | when:
564 | def result = match([a: null], [a: null])
565 |
566 | then:
567 | result.ok()
568 | }
569 |
570 | def "any expectation can be root expectation"() {
571 | when:
572 | def result = matcherFactory.matcher()
573 | .expected(expected)
574 | .actual(toJson(actual))
575 | .match()
576 |
577 | then:
578 | !result.ok()
579 | result.mismatch().expectationMismatch() == mismatch
580 | result.mismatch().path() == path
581 |
582 | where:
583 | expected | actual || path | mismatch
584 | [1, 2, 3] | [1, 88, 3] || '$.1' | primitiveValueMismatch(2, 88)
585 | [1, 2, 3] as Set | [3, 1, 99] || '$' | new ArrayLackingElementsMismatch([expectation(2)] as Set)
586 | 'medicine' | 'drug' || '$' | primitiveValueMismatch('medicine', 'drug')
587 | 100 | 99 || '$' | primitiveValueMismatch(100, 99)
588 | true | false || '$' | primitiveValueMismatch(true, false)
589 | null | 'vegetables' || '$' | new NotNullMismatch(testActualFactory.actual('vegetables'))
590 | [a: 1] | [a: 9] || '$.a' | primitiveValueMismatch(1, 9)
591 | }
592 |
593 | def "Groovy's GString can be a key in expectation"() {
594 | given:
595 | String key = 'name'
596 |
597 | when:
598 | def result = match(["$key": 'Andreas'], [name: 'Andreas'])
599 |
600 | then:
601 | noExceptionThrown()
602 | result.ok()
603 | }
604 |
605 | private static MatchResult match(Map expected, Map actual) {
606 | matcherFactory.matcher()
607 | .expected(expected)
608 | .actual(toJson(actual))
609 | .match()
610 | }
611 |
612 | private static PrimitiveValueMismatch primitiveValueMismatch(def expected, def actual) {
613 | return new PrimitiveValueMismatch(testActualFactory.actual(expected), testActualFactory.actual(actual))
614 | }
615 |
616 | private static JsonExpectation expectation(Object object) {
617 | if (object == null) {
618 | return new NullExpectation()
619 | }
620 | return primitive(object)
621 | }
622 |
623 | private static PrimitiveValueExpectation primitive(Object object) {
624 | return new PrimitiveValueExpectation(testActualFactory.actual(object))
625 | }
626 | }
627 |
--------------------------------------------------------------------------------
/jazon-core/src/test/groovy/com/zendesk/jazon/MessagesSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 | import com.zendesk.jazon.actual.factory.GsonActualFactory
4 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
5 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
6 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
7 | import spock.lang.Specification
8 | import spock.lang.Unroll
9 |
10 | import static groovy.json.JsonOutput.toJson
11 |
12 | class MessagesSpec extends Specification {
13 | private static final MatcherFactory matcherFactory = new MatcherFactory(
14 | new TranslatorFacade(DefaultTranslators.translators() + JazonTypesTranslators.translators()),
15 | new GsonActualFactory()
16 | )
17 |
18 | def "object expectation - primitive field value mismatch"() {
19 | given:
20 | def expected = [
21 | a: expectedValue
22 | ]
23 | def actual = [
24 | a: actualValue
25 | ]
26 |
27 | when:
28 | def result = match(expected, actual)
29 |
30 | then:
31 | result.message() == "Mismatch at path: \$.a\nExpected: $expectedInMessage\nActual: $actualInMessage"
32 | print result
33 |
34 | where:
35 | expectedValue | actualValue || expectedInMessage | actualInMessage
36 | 'lance' | 'vance' || '"lance"' | '"vance"'
37 | 120 | 50 || '120' | '50'
38 | 44.50 | 180.10 || '44.50' | '180.10'
39 | true | false || 'true' | 'false'
40 | }
41 |
42 | def "object expectation - unexpected field"() {
43 | given:
44 | def expected = [
45 | a: 'lance'
46 | ]
47 | def actual = [
48 | a: 'lance',
49 | b: 'helicopter'
50 | ]
51 |
52 | when:
53 | def result = match(expected, actual)
54 |
55 | then:
56 | result.message() == 'Mismatch at path: $\nUnexpected field "b" in object.'
57 | print result
58 | }
59 |
60 | @Unroll
61 | def "object expectation - no field (#expectedValue)"() {
62 | given:
63 | def expected = [
64 | a: expectedValue
65 | ]
66 | def actual = [
67 | b: 'strong wind'
68 | ]
69 |
70 | when:
71 | def result = match(expected, actual)
72 |
73 | then:
74 | result.message() == "Mismatch at path: \$\nCould not find expected field (\"a\": $expectedInMessage)"
75 | print result
76 |
77 | where:
78 | expectedValue | expectedInMessage
79 | 'lance' | '"lance"'
80 | 120 | '120'
81 | true | 'true'
82 | false | 'false'
83 | null | 'null'
84 | [1, 2, 3] | '[1, 2, 3]'
85 | ['milk', 'sugar', 'flour'] | '["milk", "sugar", "flour"]'
86 | [1, 2, 3] as Set | '[1, 2, 3] (unordered)'
87 | [name: 'Wayne', surname: 'Rooney'] | '{"name": "Wayne", "surname": "Rooney"}'
88 | [uno: 1, due: 2, tres: 3, quatro: 4] | '{"uno": 1, "due": 2, ...}'
89 | }
90 |
91 | def "object expectation: found null instead of #expectedValue"() {
92 | given:
93 | def expected = [
94 | a: 'refrigerator'
95 | ]
96 | def actual = [
97 | a: null
98 | ]
99 |
100 | when:
101 | def result = match(expected, actual)
102 |
103 | then:
104 | result.message() == 'Mismatch at path: \$.a\nFound null. Expected: "refrigerator"'
105 | print result
106 | }
107 |
108 | def "object expectation: found something instead of null"() {
109 | given:
110 | def expected = [
111 | a: null
112 | ]
113 | def actual = [
114 | a: 'refrigerator'
115 | ]
116 |
117 | when:
118 | def result = match(expected, actual)
119 |
120 | then:
121 | result.message() == 'Mismatch at path: \$.a\nExpected null. Found: "refrigerator"'
122 | print result
123 | }
124 |
125 | def "ordered array expectation: unexpected elements"() {
126 | given:
127 | def expected = [
128 | a: ['red', 'green', 'blue']
129 | ]
130 | def actual = [
131 | a: ['red', 'green', 'blue', 'silver', 'black']
132 | ]
133 |
134 | when:
135 | def result = match(expected, actual)
136 |
137 | then:
138 | result.message() == 'Mismatch at path: \$.a\nArray contains unexpected items: ["silver", "black"]'
139 | print result
140 | }
141 |
142 | def "ordered array expectation: lacking elements"() {
143 | given:
144 | def expected = [
145 | a: ['red', 'green', 'blue', 'silver', 'black']
146 | ]
147 | def actual = [
148 | a: ['red', 'green']
149 | ]
150 |
151 | when:
152 | def result = match(expected, actual)
153 |
154 | then:
155 | result.message() == 'Mismatch at path: \$.a\nArray lacks the items: ["blue", "silver", "black"]'
156 | print result
157 | }
158 |
159 | def "ordered array expectation: unexpected element as object"() {
160 | given:
161 | def expected = [
162 | items: [
163 | [firstname: 'Jack', lastname: 'Bauer'],
164 | [firstname: 'Franz', lastname: 'Beckenbauer']
165 | ]
166 | ]
167 | def actual = [
168 | items: [
169 | [firstname: 'Jack', lastname: 'Bauer'],
170 | [firstname: 'Franz', lastname: 'Beckenbauer'],
171 | [firstname: 'Oliver', lastname: 'Twist', position: [x: 1, y: 1]]
172 | ]
173 | ]
174 |
175 | when:
176 | def result = match(expected, actual)
177 |
178 | then:
179 | result.message() == 'Mismatch at path: \$.items\nArray contains unexpected items: [{"firstname": "Oliver", "lastname": "Twist", "position": {"x": 1, "y": 1}}]'
180 | print result
181 | }
182 |
183 | def "ordered array expectation: unexpected element as array"() {
184 | given:
185 | def expected = [
186 | items: [
187 | ['red', 'green', 'blue'],
188 | ['cyan', 'magenta', 'yellow']
189 | ]
190 | ]
191 | def actual = [
192 | items: [
193 | ['red', 'green', 'blue'],
194 | ['cyan', 'magenta', 'yellow'],
195 | ['huehue', 'hue', 'alpha', 'saturn', 'jupiter'],
196 | ['finito']
197 | ]
198 | ]
199 |
200 | when:
201 | def result = match(expected, actual)
202 |
203 | then:
204 | result.message() == 'Mismatch at path: \$.items\nArray contains unexpected items: [["huehue", "hue", "alpha", "saturn", "jupiter"], ["finito"]]'
205 | print result
206 | }
207 |
208 | MatchResult match(Map expected, Map actual) {
209 | matcherFactory.matcher()
210 | .expected(expected)
211 | .actual(toJson(actual))
212 | .match()
213 | }
214 |
215 | private static void print(MatchResult result) {
216 | println result.message() + '\n'
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/jazon-core/src/test/groovy/com/zendesk/jazon/MismatchPathSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 | import com.zendesk.jazon.actual.factory.GsonActualFactory
4 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
5 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
6 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
7 | import com.zendesk.jazon.mismatch.impl.PrimitiveValueMismatch
8 | import spock.lang.Specification
9 |
10 | import static groovy.json.JsonOutput.toJson
11 |
12 | class MismatchPathSpec extends Specification {
13 | private static final TestActualFactory testActualFactory = new TestActualFactory()
14 | private static final MatcherFactory matcherFactory = new MatcherFactory(
15 | new TranslatorFacade(DefaultTranslators.translators() + JazonTypesTranslators.translators()),
16 | new GsonActualFactory()
17 | )
18 |
19 | def "complex case"() {
20 | given:
21 | def expected = [
22 | data: [
23 | [key: 111, values: [a: 1, b: 2]],
24 | [key: 111, values: [a: 2, b: 4]],
25 | [key: 111, values: [a: 3, b: 8]],
26 | [key: 111, values: [a: 4, b: 16]],
27 | ]
28 | ]
29 | def actual = [
30 | data: [
31 | [key: 111, values: [a: 1, b: 2]],
32 | [key: 111, values: [a: 2, b: 4]],
33 | [key: 111, values: [a: 3, b: 99]],
34 | [key: 111, values: [a: 4, b: 16]],
35 | ]
36 | ]
37 |
38 | when:
39 | def result = match(expected, actual)
40 |
41 | then:
42 | !result.ok()
43 | result.mismatch().expectationMismatch() == primitiveValueMismatch(8, 99)
44 | result.mismatch().path() == '$.data.2.values.b'
45 | }
46 |
47 | def "complex case 2"() {
48 | given:
49 | def expected = VERY_COMPLEX_OBJECT
50 | def actual = VERY_COMPLEX_OBJECT_WITH_ONE_FIELD_DIFFERENT
51 |
52 | when:
53 | def result = match(expected, actual)
54 |
55 | then:
56 | !result.ok()
57 | result.mismatch().expectationMismatch() == primitiveValueMismatch("green", "black")
58 | result.mismatch().path() == '$.data.1.values.elements.0.value'
59 | }
60 |
61 | private static MatchResult match(Map expected, Map actual) {
62 | matcherFactory.matcher()
63 | .expected(expected)
64 | .actual(toJson(actual))
65 | .match()
66 | }
67 |
68 | private static PrimitiveValueMismatch primitiveValueMismatch(def expected, def actual) {
69 | return new PrimitiveValueMismatch(testActualFactory.actual(expected), testActualFactory.actual(actual))
70 | }
71 |
72 | private static final VERY_COMPLEX_OBJECT = [
73 | data: [
74 | [
75 | key : 111,
76 | values: [
77 | a : 1,
78 | b : "one",
79 | elements: [
80 | [
81 | name: "color",
82 | value: "blue"
83 | ],
84 | [
85 | name: "width",
86 | value: 5
87 | ],
88 | [
89 | name: "height",
90 | value: 10
91 | ],
92 | ]
93 | ]
94 | ],
95 | [
96 | key : 111,
97 | values: [
98 | a: 2,
99 | b: "two",
100 | elements: [
101 | [
102 | name: "color",
103 | value: "green"
104 | ],
105 | [
106 | name: "width",
107 | value: 12
108 | ],
109 | [
110 | name: "height",
111 | value: 24
112 | ],
113 | ]
114 | ]
115 | ],
116 | [
117 | key : 111,
118 | values: [
119 | a: 3,
120 | b: "three",
121 | elements: [
122 | [
123 | name: "color",
124 | value: "red"
125 | ],
126 | [
127 | name: "width",
128 | value: 33
129 | ],
130 | [
131 | name: "height",
132 | value: 66
133 | ],
134 | ]
135 | ]
136 | ],
137 | [
138 | key : 111,
139 | values: [
140 | a: 4,
141 | b: "four",
142 | elements: [
143 | [
144 | name: "color",
145 | value: "orange"
146 | ],
147 | [
148 | name: "width",
149 | value: 25
150 | ],
151 | [
152 | name: "height",
153 | value: 50
154 | ],
155 | ]
156 | ]
157 | ],
158 | ]
159 | ]
160 |
161 | private static final VERY_COMPLEX_OBJECT_WITH_ONE_FIELD_DIFFERENT = [
162 | data: [
163 | [
164 | key : 111,
165 | values: [
166 | a : 1,
167 | b : "one",
168 | elements: [
169 | [
170 | name: "color",
171 | value: "blue"
172 | ],
173 | [
174 | name: "width",
175 | value: 5
176 | ],
177 | [
178 | name: "height",
179 | value: 10
180 | ],
181 | ]
182 | ]
183 | ],
184 | [
185 | key : 111,
186 | values: [
187 | a: 2,
188 | b: "two",
189 | elements: [
190 | [
191 | name: "color",
192 | value: "black"
193 | ],
194 | [
195 | name: "width",
196 | value: 12
197 | ],
198 | [
199 | name: "height",
200 | value: 24
201 | ],
202 | ]
203 | ]
204 | ],
205 | [
206 | key : 111,
207 | values: [
208 | a: 3,
209 | b: "three",
210 | elements: [
211 | [
212 | name: "color",
213 | value: "red"
214 | ],
215 | [
216 | name: "width",
217 | value: 33
218 | ],
219 | [
220 | name: "height",
221 | value: 66
222 | ],
223 | ]
224 | ]
225 | ],
226 | [
227 | key : 111,
228 | values: [
229 | a: 4,
230 | b: "four",
231 | elements: [
232 | [
233 | name: "color",
234 | value: "orange"
235 | ],
236 | [
237 | name: "width",
238 | value: 25
239 | ],
240 | [
241 | name: "height",
242 | value: 50
243 | ],
244 | ]
245 | ]
246 | ],
247 | ]
248 | ]
249 | }
250 |
--------------------------------------------------------------------------------
/jazon-core/src/test/groovy/com/zendesk/jazon/actual/ActualJsonNumberSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.actual
2 |
3 | import spock.lang.Specification
4 | import spock.lang.Unroll
5 |
6 | class ActualJsonNumberSpec extends Specification {
7 |
8 | def "equals() returns true, hashCode() equals"() {
9 | given:
10 | def fooNumber = new ActualJsonNumber(foo as Number)
11 | def barNumber = new ActualJsonNumber(bar as Number)
12 |
13 | expect:
14 | fooNumber.equals(barNumber)
15 | fooNumber.hashCode() == barNumber.hashCode()
16 |
17 | and:
18 | barNumber.equals(fooNumber)
19 | barNumber.hashCode() == fooNumber.hashCode()
20 |
21 | where:
22 | foo | bar
23 | 1 as int | 1 as int
24 | 1 as long | 1 as long
25 | 1 as long | 1 as int
26 | 1.1d | 1.1d
27 | 1.1f | 1.1f
28 | new BigDecimal("1.1") | new BigDecimal("1.1")
29 | }
30 |
31 | @Unroll
32 | def "hashCode() differs and equals() returns false"() {
33 | given:
34 | def fooNumber = new ActualJsonNumber(foo)
35 | def barNumber = new ActualJsonNumber(bar)
36 |
37 | expect:
38 | fooNumber.hashCode() != barNumber.hashCode()
39 | !fooNumber.equals(barNumber)
40 |
41 | and:
42 | barNumber.hashCode() != fooNumber.hashCode()
43 | !barNumber.equals(fooNumber)
44 |
45 | where:
46 | foo | bar
47 | 1 as int | 2 as int
48 | 1 as long | 2 as long
49 | 1 as int | 2 as long
50 | 1 as long | 2 as int
51 | 1.2f | 1 as int
52 | 1.2d | 1 as int
53 | 1.2f | 1.4f
54 | 1.2f | 1.4d
55 | new BigDecimal('1.000000000000001') | new BigDecimal('1.0000000000000011')
56 | 1.000000000000001d | new BigDecimal('1.0000000000000011')
57 | 1.000000000000001f | new BigDecimal('1.0000000000000011')
58 | new BigDecimal('1.000000000000000000000000001') | new BigDecimal('1.0000000000000000000000000011')
59 | 1.1f | new BigDecimal('1.1') // Doesn't match because the types differ.
60 | 1.1d | new BigDecimal('1.1') // Doesn't match because the types differ.
61 | 1 as int | 1.0f // Doesn't match because the types differ.
62 | 1 as int | 1.0d // Doesn't match because the types differ.
63 | 1 as long | 1.0f // Doesn't match because the types differ.
64 | 1 as long | 1.0d // Doesn't match because the types differ.
65 | 1.1f | 1.1d // Doesn't match because the types differ.
66 | }
67 |
68 | def "hashCode() equals and equals() returns false"() {
69 | given:
70 | def fooNumber = new ActualJsonNumber(foo)
71 | def barNumber = new ActualJsonNumber(bar)
72 |
73 | expect:
74 | fooNumber.hashCode() == barNumber.hashCode()
75 | !fooNumber.equals(barNumber)
76 |
77 | and:
78 | barNumber.hashCode() == fooNumber.hashCode()
79 | !barNumber.equals(fooNumber)
80 |
81 | where:
82 | foo | bar
83 | ((long) Integer.MAX_VALUE + 1) as long | Integer.MIN_VALUE as int
84 | ((long) Integer.MAX_VALUE + 1) as long | Integer.MIN_VALUE as long
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/jazon-core/src/test/groovy/com/zendesk/jazon/mismatch/TypeMismatchSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.mismatch
2 |
3 | import com.zendesk.jazon.actual.ActualJsonArray
4 | import com.zendesk.jazon.actual.ActualJsonBoolean
5 | import com.zendesk.jazon.actual.ActualJsonNumber
6 | import com.zendesk.jazon.actual.ActualJsonObject
7 | import com.zendesk.jazon.actual.ActualJsonString
8 | import com.zendesk.jazon.mismatch.impl.TypeMismatch
9 | import spock.lang.Specification
10 |
11 | class TypeMismatchSpec extends Specification {
12 |
13 | def "displays good message"() {
14 | given:
15 | def mismatch = new TypeMismatch(expectedType, actualType)
16 |
17 | expect:
18 | mismatch.message() == "Expected type: $expectedTypeAsString\nActual type: $actualTypeAsString"
19 | println mismatch.message() + '\n'
20 |
21 | where:
22 | expectedType | actualType || expectedTypeAsString | actualTypeAsString
23 | ActualJsonObject | ActualJsonArray || 'Object' | 'Array'
24 | ActualJsonArray | ActualJsonNumber || 'Array' | 'Number'
25 | ActualJsonNumber | ActualJsonString || 'Number' | 'String'
26 | ActualJsonBoolean | ActualJsonObject || 'Boolean' | 'Object'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jazon-core/src/test/java/com/zendesk/jazon/TestActualFactory.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon;
2 |
3 | import com.zendesk.jazon.actual.Actual;
4 | import com.zendesk.jazon.actual.factory.ActualFactory;
5 | import com.zendesk.jazon.actual.ActualJsonArray;
6 | import com.zendesk.jazon.actual.ActualJsonBoolean;
7 | import com.zendesk.jazon.actual.ActualJsonNull;
8 | import com.zendesk.jazon.actual.ActualJsonNumber;
9 | import com.zendesk.jazon.actual.ActualJsonObject;
10 | import com.zendesk.jazon.actual.ActualJsonString;
11 |
12 | import java.util.LinkedHashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | import static java.util.stream.Collectors.toList;
17 | import static java.util.stream.Collectors.toMap;
18 |
19 | /**
20 | * This class exists to handle assertions in tests when instance of Actual is needed in the assertion-expectation - e.g.
21 | * equality-comparing of `Mismatch` instances that store Actual instance inside itself.
22 | * Without this class we would have to write lot of verbose constructor-calls like `new ActualJsonArray(...)` etc.
23 | */
24 | public class TestActualFactory implements ActualFactory {
25 |
26 | @Override
27 | public Actual actual(Object input) {
28 | if (input instanceof Map) {
29 | return actualObject((Map) input);
30 | } else if (input instanceof Number) {
31 |
32 | return new ActualJsonNumber((Number) input);
33 | } else if (input instanceof String) {
34 | return new ActualJsonString((String) input);
35 | } else if (input == null) {
36 | return ActualJsonNull.INSTANCE;
37 | } else if (input instanceof List) {
38 | return actualArray((List) input);
39 | } else if (input instanceof Boolean) {
40 | return new ActualJsonBoolean((Boolean) input);
41 | }
42 | throw new IllegalArgumentException();
43 | }
44 |
45 | private ActualJsonObject actualObject(Map objectsMap) {
46 | Map map = objectsMap.entrySet()
47 | .stream()
48 | .collect(
49 | toMap(
50 | Map.Entry::getKey,
51 | e -> actual(e.getValue()),
52 | (a, b) -> a,
53 | LinkedHashMap::new
54 | )
55 | );
56 | return new ActualJsonObject(map);
57 | }
58 |
59 | private ActualJsonArray actualArray(List objects) {
60 | List actuals = objects.stream()
61 | .map(this::actual)
62 | .collect(toList());
63 | return new ActualJsonArray(actuals);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/jazon-junit/README.md:
--------------------------------------------------------------------------------
1 | # Jazon JUnit
2 | A library for test assertions on JSON payloads - for JUnit framework.
3 |
4 | ## Quickstart
5 |
6 | #### Example 1: Simple exact-match
7 |
8 | For such JSON:
9 | ```json
10 | {
11 | "firstname": "Steve",
12 | "lastname": "Jobs"
13 | }
14 | ```
15 |
16 | ... an exact-match assertion would look like this:
17 |
18 | ```java
19 | @Test
20 | public void simpleTest() {
21 | // when
22 | String actualJson = getSteveJobsJson();
23 |
24 | // then
25 | assertThat(response).matches(
26 | new JazonMap()
27 | .with("firstname", "Steve")
28 | .with("lastname", "Jobs")
29 | );
30 | }
31 | ```
32 |
33 | #### Example 2: Unordered array
34 |
35 | This assertion passes even though the items in the array are in different order.
36 |
37 | The `actualJson`:
38 | ```json
39 | {
40 | "id": 95478,
41 | "name": "Coca Cola",
42 | "tags": ["sprite", "pepsi", "7up", "fanta", "dr pepper"]
43 | }
44 | ```
45 |
46 | The assertion:
47 | ```java
48 | assertThat(response).matches(
49 | new JazonMap()
50 | .with("id", 95478)
51 | .with("name", "Coca Cola")
52 | .with("tags", set("pepsi", "dr pepper", "sprite", "fanta", "7up"))
53 | );
54 | ```
55 | ```java
56 | private Set set(Object... elements) {
57 | HashSet result = new HashSet<>(elements.length);
58 | result.addAll(asList(elements));
59 | return result;
60 | }
61 | ```
62 |
63 | #### Example 3: Custom assertions
64 |
65 | If you need, instead of exact-matching, you can define custom assertions using Predicates.
66 | Here for example, we used custom assertions:
67 | * to check that a number is in given range - `(Integer id) -> id >= 0`
68 | * to check that a field matches a regex - `regex()`
69 | * to check that a field just exists, no matter of its value - `Objects::nonNull`
70 |
71 | The `actualJson`:
72 | ```json
73 | {
74 | "id": 95478,
75 | "name": "Coca Cola",
76 | "value": "133.30",
77 | "updated_at": "1990-06-19T12:19:10Z"
78 | }
79 | ```
80 |
81 | The assertion:
82 | ```java
83 | assertThat(response).matches(
84 | new JazonMap()
85 | .with("id", (Integer id) -> id >= 0)
86 | .with("name", "Coca Cola")
87 | .with("value", regex("\\d+\\.\\d\\d"))
88 | .with("updated_at", Objects::nonNull)
89 | );
90 | ```
91 |
92 | ```java
93 | private Predicate regex(String regex) {
94 | return value -> value.matches(regex);
95 | }
96 | ```
97 |
98 | #### Example 4: Utils extraction
99 |
100 | To avoid code duplication, you can extract your common wildcard-assertions to constants.
101 |
102 | ```java
103 | assertThat(response).matches(
104 | new JazonMap()
105 | .with("id", ANY_ID) // a constant
106 | .with("name", "Coca Cola")
107 | .with("value", "133.30")
108 | .with("updated_at", ANY_ISO_DATETIME) // a constant
109 | );
110 | ```
111 | ```java
112 | private static final Predicate ANY_ID = (id) -> id >= 0;
113 | private static final Predicate ANY_ISO_DATETIME =
114 | datetime -> datetime.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
115 | ```
116 |
117 | #### Example 5: Utils extraction to domain objects
118 |
119 | To avoid code duplication even more, you can extract the parts of JSON. This will also
120 | make your tests more readable.
121 | Here in the examples we extract `deal()` - a business object from sales domain.
122 |
123 | ```java
124 | assertThat(response).matches(deal("Coca Cola", "10.00"));
125 | ```
126 | ```java
127 | assertThat(response).matches(
128 | asList(
129 | deal("Coca Cola", "10.00"),
130 | deal("Pepsi", "9.00"),
131 | deal("Fanta", "10.00"),
132 | deal("Sprite", "10.00"),
133 | deal("Dr Pepper", "12.00")
134 | )
135 | );
136 | ```
137 | ```java
138 | private JazonMap deal(String name, String value) {
139 | return new JazonMap()
140 | .with("id", ANY_ID)
141 | .with("name", name)
142 | .with("value", value)
143 | .with("updated_at", ANY_ISO_DATETIME);
144 | }
145 | ```
146 |
147 | #### Examples code
148 | You can check out the example tests [in the code](/examples/src/test/java/com/zendesk/jazon/junit/ReadmeExamplesTest.java)
149 |
150 | ## Copyright and license
151 | Copyright 2019 Zendesk, Inc.
152 |
153 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
154 | You may obtain a copy of the License at
155 | http://www.apache.org/licenses/LICENSE-2.0
156 |
157 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
158 |
159 |
--------------------------------------------------------------------------------
/jazon-junit/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | name = 'Jazon JUnit Adapter'
3 | description = 'A library for test assertions on JSON payloads - for JUnit framework.'
4 | }
5 |
6 | apply from: '../gradle/publishing.gradle'
7 |
8 | dependencies {
9 | compile project(':jazon-core')
10 |
11 | compileOnly 'org.projectlombok:lombok:1.18.12'
12 | annotationProcessor 'org.projectlombok:lombok:1.18.12'
13 |
14 | testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.1'
15 | }
16 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/expectation/translator/JunitTranslators.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.expectation.JsonExpectation;
4 | import com.zendesk.jazon.expectation.impl.ObjectExpectation;
5 | import com.zendesk.jazon.expectation.impl.PredicateExpectation;
6 | import com.zendesk.jazon.junit.JazonMap;
7 | import com.zendesk.jazon.junit.JsonExpectationInput;
8 | import com.zendesk.jazon.junit.ObjectExpectationInput;
9 | import com.zendesk.jazon.junit.PredicateExpectationInput;
10 |
11 | import java.util.HashMap;
12 | import java.util.LinkedHashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | import static java.util.Arrays.asList;
17 | import static java.util.stream.Collectors.toMap;
18 |
19 | public class JunitTranslators {
20 | public static List> translators() {
21 | return asList(
22 | new TranslatorMapping<>(ObjectExpectationInput.class, new ObjectExpectationInputTranslator()),
23 | new TranslatorMapping<>(PredicateExpectationInput.class, new PredicateExpectationInputTranslator()),
24 | new TranslatorMapping<>(JazonMap.class, new JazonMapTranslator())
25 | );
26 | }
27 |
28 | private static class ObjectExpectationInputTranslator implements Translator {
29 | @Override
30 | public JsonExpectation jsonExpectation(ObjectExpectationInput objectExpectationInput, TranslatorFacade translator) {
31 | return translator.expectation(objectExpectationInput.object());
32 | }
33 | }
34 |
35 | private static class PredicateExpectationInputTranslator implements Translator {
36 | @Override
37 | public JsonExpectation jsonExpectation(PredicateExpectationInput predicateExpectationInput, TranslatorFacade translator) {
38 | return new PredicateExpectation(predicateExpectationInput.predicate());
39 | }
40 | }
41 |
42 | private static class JazonMapTranslator implements Translator {
43 | @Override
44 | public JsonExpectation jsonExpectation(JazonMap jazonMap, TranslatorFacade translator) {
45 | HashMap map = copied(jazonMap.map());
46 | //FIXME code duplication
47 | LinkedHashMap expectationsMap = map.entrySet()
48 | .stream()
49 | .collect(
50 | toMap(
51 | e -> e.getKey().toString(),
52 | e -> translator.expectation(e.getValue()),
53 | (a, b) -> a,
54 | () -> new LinkedHashMap<>(map.size())
55 | )
56 | );
57 | return new ObjectExpectation(expectationsMap);
58 | }
59 |
60 | /**
61 | * Returns Map converted from Map
62 | */
63 | private HashMap copied(Map map) {
64 | return map
65 | .entrySet()
66 | .stream()
67 | .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, HashMap::new));
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/JazonJunitAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.MatcherFactory;
5 | import com.zendesk.jazon.actual.factory.GsonActualFactory;
6 | import com.zendesk.jazon.expectation.translator.DefaultTranslators;
7 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators;
8 | import com.zendesk.jazon.expectation.translator.JunitTranslators;
9 | import com.zendesk.jazon.expectation.translator.TranslatorFacade;
10 | import com.zendesk.jazon.expectation.translator.TranslatorMapping;
11 |
12 | import java.util.LinkedList;
13 | import java.util.List;
14 |
15 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
16 |
17 | public class JazonJunitAdapter {
18 | private static final MatcherFactory matcherFactory = new MatcherFactory(
19 | new TranslatorFacade(translators()),
20 | new GsonActualFactory()
21 | );
22 |
23 | private final String actualJson;
24 |
25 | public JazonJunitAdapter(String actualJson) {
26 | this.actualJson = checkNotNull(actualJson);
27 | }
28 |
29 | public static JazonJunitAdapter assertThat(String actualJson) {
30 | return new JazonJunitAdapter(actualJson);
31 | }
32 |
33 | public void matches(JazonMap jazonMap) {
34 | matchExpectedObject(jazonMap.map());
35 | }
36 |
37 | public void matches(Object expected) {
38 | matchExpectedObject(expected);
39 | }
40 |
41 | private void matchExpectedObject(Object expected) {
42 | MatchResult matchResult = matcherFactory.matcher()
43 | .expected(expected)
44 | .actual(actualJson)
45 | .match();
46 | if (matchResult.ok()) {
47 | return;
48 | }
49 | String mismatchMessageTemplate = "\n-----------------------------------\nJSON MISMATCH:\n%s\n-----------------------------------\n";
50 | throw new AssertionError(String.format(mismatchMessageTemplate, matchResult.message()));
51 | }
52 |
53 | private static List> translators() {
54 | List> result = new LinkedList<>();
55 | result.addAll(DefaultTranslators.translators());
56 | result.addAll(JazonTypesTranslators.translators());
57 | result.addAll(JunitTranslators.translators());
58 | return result;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/JazonList.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import lombok.EqualsAndHashCode;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.function.Predicate;
8 |
9 | import static com.zendesk.jazon.util.Preconditions.checkNotNull;
10 |
11 | /**
12 | * This class exists to allow to pass a lambda-predicate to the same interface as other typical objects like String,
13 | * Integer, List, etc. are passed. This is due to the limitation that {@code Object} is not effectively a supertype of
14 | * lambda expression.
15 | */
16 | @EqualsAndHashCode
17 | public class JazonList {
18 | private final List list = new ArrayList<>();
19 |
20 | public JazonList(Predicate>... predicates) {
21 | checkNotNull(predicates);
22 | for (Predicate> element : predicates) {
23 | list.add(new PredicateExpectationInput<>(element));
24 | }
25 | }
26 |
27 | public JazonList(Object... objects) {
28 | checkNotNull(objects);
29 | for (Object element : objects) {
30 | list.add(new ObjectExpectationInput(element));
31 | }
32 | }
33 |
34 | public JazonList with(Object object) {
35 | list.add(new ObjectExpectationInput(object));
36 | return this;
37 | }
38 |
39 | public JazonList with(Predicate predicate) {
40 | list.add(new PredicateExpectationInput<>(predicate));
41 | return this;
42 | }
43 |
44 | public List list() {
45 | return list;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/JazonMap.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import lombok.EqualsAndHashCode;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.function.Predicate;
8 |
9 | /**
10 | * This class exists to allow to pass a lambda-predicate to the same interface as other typical objects like String,
11 | * Integer, List, etc. are passed. This is due to the limitation that {@code Object} is not effectively a supertype of
12 | * lambda expression.
13 | */
14 | @EqualsAndHashCode
15 | public class JazonMap {
16 | private final Map map = new HashMap<>();
17 |
18 | public JazonMap with(String fieldName, Object fieldValue) {
19 | map.put(fieldName, new ObjectExpectationInput(fieldValue));
20 | return this;
21 | }
22 |
23 | public JazonMap with(String fieldName, Predicate predicate) {
24 | map.put(fieldName, new PredicateExpectationInput<>(predicate));
25 | return this;
26 | }
27 |
28 | public Map map() {
29 | return map;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/JsonExpectationInput.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | /**
4 | * A marker interface. It exists only to have a common supertype to store the instances of its subtypes in a single
5 | * collection.
6 | */
7 | public interface JsonExpectationInput {
8 | // intentionally left blank
9 | }
10 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/ObjectExpectationInput.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.ToString;
5 |
6 | @ToString
7 | @EqualsAndHashCode
8 | public class ObjectExpectationInput implements JsonExpectationInput {
9 | private final Object object;
10 |
11 | ObjectExpectationInput(Object object) {
12 | this.object = object;
13 | }
14 |
15 | public Object object() {
16 | return object;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/jazon-junit/src/main/java/com/zendesk/jazon/junit/PredicateExpectationInput.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.ToString;
5 |
6 | import java.util.function.Predicate;
7 |
8 | @ToString
9 | @EqualsAndHashCode
10 | public class PredicateExpectationInput implements JsonExpectationInput {
11 | private final Predicate predicate;
12 |
13 | PredicateExpectationInput(Predicate predicate) {
14 | this.predicate = predicate;
15 | }
16 |
17 | public Predicate> predicate() {
18 | return predicate;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/jazon-junit/src/test/java/com/zendesk/jazon/junit/JazonListTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.List;
6 | import java.util.function.Predicate;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 | import static org.junit.jupiter.api.Assertions.assertFalse;
10 | import static org.junit.jupiter.api.Assertions.assertThrows;
11 | import static org.junit.jupiter.api.Assertions.assertTrue;
12 |
13 | class JazonListTest {
14 |
15 | @Test
16 | void onlySimpleTypes() {
17 | // given
18 | JazonList jazonList = new JazonList("orange", 55, false, 173.50, null);
19 |
20 | // when
21 | List list = jazonList.list();
22 |
23 | // then
24 | assertEquals(5, list.size());
25 | assertEquals(new ObjectExpectationInput("orange"), list.get(0));
26 | assertEquals(new ObjectExpectationInput(55), list.get(1));
27 | assertEquals(new ObjectExpectationInput(false), list.get(2));
28 | assertEquals(new ObjectExpectationInput(173.50), list.get(3));
29 | assertEquals(new ObjectExpectationInput(null), list.get(4));
30 | }
31 |
32 | @Test
33 | void nullIsTranslatedToNullPredicate_usingConstructor() {
34 | // given
35 | JazonList jazonList = new JazonList(null, null);
36 |
37 | // when
38 | List list = jazonList.list();
39 |
40 | // then
41 | assertEquals(2, list.size());
42 | assertEquals(new PredicateExpectationInput<>(null), list.get(0));
43 | }
44 |
45 | @Test
46 | void nullIsTranslatedToNullPredicate_usingModifierMethod() {
47 | // given
48 | JazonList jazonList = new JazonList().with(null);
49 |
50 | // when
51 | List list = jazonList.list();
52 |
53 | // then
54 | assertEquals(1, list.size());
55 | assertEquals(new PredicateExpectationInput<>(null), list.get(0));
56 | }
57 |
58 | @Test
59 | void failsForAloneNullInConstructor() {
60 | assertThrows(NullPointerException.class, () -> new JazonList(null));
61 | }
62 |
63 | @Test
64 | void onlyPredicates() {
65 | // given
66 | JazonList jazonList = new JazonList((Integer it) -> it > 0, (String s) -> s.matches("re.*"));
67 |
68 | // when
69 | List list = jazonList.list();
70 |
71 | // then
72 | assertEquals(2, list.size());
73 | PredicateExpectationInput firstPredicateInput = (PredicateExpectationInput) list.get(0);
74 | Predicate firstPredicate = firstPredicateInput.predicate();
75 | assertFalse(firstPredicate.test(-10));
76 | assertFalse(firstPredicate.test(-1));
77 | assertFalse(firstPredicate.test(0));
78 | assertTrue(firstPredicate.test(1));
79 | assertTrue(firstPredicate.test(10));
80 |
81 | PredicateExpectationInput secondPredicateInput = (PredicateExpectationInput) list.get(1);
82 | Predicate secondPredicate = secondPredicateInput.predicate();
83 | assertTrue(secondPredicate.test("red"));
84 | assertTrue(secondPredicate.test("reindeer"));
85 | assertFalse(secondPredicate.test("black"));
86 | assertFalse(secondPredicate.test("octopus"));
87 | }
88 |
89 | @Test
90 | void nestedMap() {
91 | // given
92 | JazonMap robert = new JazonMap()
93 | .with("firstname", "Robert")
94 | .with("firstname", "Kubica");
95 | JazonMap jenson = new JazonMap()
96 | .with("firstname", "Jenson")
97 | .with("firstname", "Button");
98 | JazonList jazonList = new JazonList(robert, jenson);
99 |
100 | // when
101 | List list = jazonList.list();
102 |
103 | // then
104 | assertEquals(2, list.size());
105 | assertEquals(new ObjectExpectationInput(robert), list.get(0));
106 | assertEquals(new ObjectExpectationInput(jenson), list.get(1));
107 | }
108 |
109 | @Test
110 | void nestedList() {
111 | // given
112 | JazonList drinks = new JazonList("pepsi", "coca cola", "sprite");
113 | JazonList birds = new JazonList("pigeon", "sparrow");
114 | JazonList jazonList = new JazonList(drinks, birds);
115 |
116 | // when
117 | List list = jazonList.list();
118 |
119 | // then
120 | assertEquals(2, list.size());
121 | assertEquals(new ObjectExpectationInput(drinks), list.get(0));
122 | assertEquals(new ObjectExpectationInput(birds), list.get(1));
123 | }
124 |
125 | @Test
126 | void allThingsAtOnce() {
127 | // given
128 | JazonMap nestedMap = new JazonMap()
129 | .with("firstname", "Robert")
130 | .with("lastname", "Kubica");
131 | JazonList drinks = new JazonList("pepsi", "coca cola", "sprite");
132 | JazonList jazonList = new JazonList()
133 | .with(150)
134 | .with(nestedMap)
135 | .with((Integer it) -> it > 100)
136 | .with("orange")
137 | .with(null)
138 | .with(drinks)
139 | .with(false);
140 |
141 | // when
142 | List list = jazonList.list();
143 |
144 | // then
145 | assertEquals(7, list.size());
146 | assertEquals(new ObjectExpectationInput(150), list.get(0));
147 | assertEquals(new ObjectExpectationInput(nestedMap), list.get(1));
148 |
149 | PredicateExpectationInput predicateInput = (PredicateExpectationInput) list.get(2);
150 | Predicate firstPredicate = predicateInput.predicate();
151 | assertFalse(firstPredicate.test(-1));
152 | assertFalse(firstPredicate.test(0));
153 | assertFalse(firstPredicate.test(10));
154 | assertFalse(firstPredicate.test(100));
155 | assertTrue(firstPredicate.test(101));
156 | assertTrue(firstPredicate.test(1000));
157 |
158 | assertEquals(new ObjectExpectationInput("orange"), list.get(3));
159 | assertEquals(new PredicateExpectationInput<>(null), list.get(4));
160 | assertEquals(new ObjectExpectationInput(drinks), list.get(5));
161 | assertEquals(new ObjectExpectationInput(false), list.get(6));
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/jazon-junit/src/test/java/com/zendesk/jazon/junit/JazonMapTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.Map;
6 | import java.util.function.Predicate;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 | import static org.junit.jupiter.api.Assertions.assertFalse;
10 | import static org.junit.jupiter.api.Assertions.assertTrue;
11 |
12 | class JazonMapTest {
13 |
14 | @Test
15 | void simpleMap() {
16 | // given
17 | JazonMap jazonMap = new JazonMap()
18 | .with("name", "Leo Messi")
19 | .with("email", "leo@messi.com")
20 | .with("height", 170)
21 | .with("nickname", null)
22 | .with("is_cool", true);
23 |
24 | // when
25 | Map map = jazonMap.map();
26 |
27 | // then
28 | assertEquals(5, map.size());
29 | assertEquals(new ObjectExpectationInput("Leo Messi"), map.get("name"));
30 | assertEquals(new ObjectExpectationInput("leo@messi.com"), map.get("email"));
31 | assertEquals(new ObjectExpectationInput(170), map.get("height"));
32 | assertEquals(new PredicateExpectationInput<>(null), map.get("nickname"));
33 | assertEquals(new ObjectExpectationInput(true), map.get("is_cool"));
34 | }
35 |
36 | @Test
37 | void mapWithNestedMap() {
38 | // given
39 | JazonMap nestedJazonMap = new JazonMap()
40 | .with("firstname", "Fernando")
41 | .with("lastname", "Alonso");
42 | JazonMap jazonMap = new JazonMap()
43 | .with("driver", nestedJazonMap);
44 |
45 | // when
46 | Map map = jazonMap.map();
47 |
48 | // then
49 | assertEquals(1, map.size());
50 | assertEquals(new ObjectExpectationInput(nestedJazonMap), map.get("driver"));
51 | }
52 |
53 | @Test
54 | void mapWithNestedLists() {
55 | // given
56 | JazonList drinks = new JazonList("pepsi", "coca cola", "sprite", "fanta");
57 | JazonList birds = new JazonList("pigeon", "sparrow");
58 | JazonMap jazonMap = new JazonMap()
59 | .with("birds", birds)
60 | .with("drinks", drinks);
61 |
62 | // when
63 | Map map = jazonMap.map();
64 |
65 | // then
66 | assertEquals(2, map.size());
67 | assertEquals(new ObjectExpectationInput(birds), map.get("birds"));
68 | assertEquals(new ObjectExpectationInput(drinks), map.get("drinks"));
69 | }
70 |
71 | @Test
72 | void mapWithPredicate() {
73 | // given
74 | JazonMap jazonMap = new JazonMap()
75 | .with("id", (Integer id) -> id > 0);
76 |
77 | // when
78 | Map map = jazonMap.map();
79 |
80 | // then
81 | assertEquals(1, map.size());
82 | PredicateExpectationInput predicateExpectationInput = (PredicateExpectationInput) map.get("id");
83 | Predicate predicate = predicateExpectationInput.predicate();
84 | assertFalse(predicate.test(-10));
85 | assertFalse(predicate.test(-1));
86 | assertFalse(predicate.test(0));
87 | assertTrue(predicate.test(1));
88 | assertTrue(predicate.test(10));
89 | }
90 |
91 | @Test
92 | void mapWithAllTheThingsAtOnce() {
93 | // given
94 | JazonMap driverMap = new JazonMap()
95 | .with("firstname", "Fernando")
96 | .with("lastname", "Alonso");
97 | JazonList drinksList = new JazonList("pepsi", "coca cola", "sprite", "fanta");
98 | JazonMap jazonMap = new JazonMap()
99 | .with("name", "Leo Messi")
100 | .with("email", "leo@messi.com")
101 | .with("nickname", null)
102 | .with("height", 170)
103 | .with("drinks", drinksList)
104 | .with("is_cool", true)
105 | .with("id", (Integer id) -> id > 0)
106 | .with("driver", driverMap);
107 |
108 | // when
109 | Map map = jazonMap.map();
110 |
111 | // then
112 | assertEquals(8, map.size());
113 | assertEquals(map.get("name"), new ObjectExpectationInput("Leo Messi"));
114 | assertEquals(map.get("email"), new ObjectExpectationInput("leo@messi.com"));
115 | assertEquals(map.get("nickname"), new PredicateExpectationInput<>(null));
116 | assertEquals(map.get("height"), new ObjectExpectationInput(170));
117 | assertEquals(map.get("drinks"), new ObjectExpectationInput(drinksList));
118 | assertEquals(map.get("is_cool"), new ObjectExpectationInput(true));
119 | assertEquals(map.get("driver"), new ObjectExpectationInput(driverMap));
120 |
121 | // and predicate from field "id" is correct
122 | PredicateExpectationInput predicateExpectationInput = (PredicateExpectationInput) map.get("id");
123 | Predicate predicate = predicateExpectationInput.predicate();
124 | assertFalse(predicate.test(-10));
125 | assertFalse(predicate.test(-1));
126 | assertFalse(predicate.test(0));
127 | assertTrue(predicate.test(1));
128 | assertTrue(predicate.test(10));
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/jazon-junit/src/test/java/com/zendesk/jazon/junit/JunitSpecificMatcherTest.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.junit;
2 |
3 | import com.zendesk.jazon.MatchResult;
4 | import com.zendesk.jazon.MatcherFactory;
5 | import com.zendesk.jazon.actual.factory.GsonActualFactory;
6 | import com.zendesk.jazon.expectation.translator.DefaultTranslators;
7 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators;
8 | import com.zendesk.jazon.expectation.translator.JunitTranslators;
9 | import com.zendesk.jazon.expectation.translator.TranslatorFacade;
10 | import com.zendesk.jazon.mismatch.impl.PredicateExecutionFailedMismatch;
11 | import com.zendesk.jazon.mismatch.impl.PrimitiveValueMismatch;
12 | import org.junit.jupiter.api.Test;
13 |
14 | import java.util.List;
15 | import java.util.stream.Collectors;
16 | import java.util.stream.Stream;
17 |
18 | import static com.zendesk.jazon.expectation.Expectations.anyNumberOf;
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertFalse;
21 | import static org.junit.jupiter.api.Assertions.assertTrue;
22 |
23 | class JunitSpecificMatcherTest {
24 | private static final MatcherFactory matcherFactory = new MatcherFactory(
25 | new TranslatorFacade(
26 | Stream.of(
27 | DefaultTranslators.translators(),
28 | JazonTypesTranslators.translators(),
29 | JunitTranslators.translators()
30 | )
31 | .flatMap(List::stream)
32 | .collect(Collectors.toList())
33 | ),
34 | new GsonActualFactory()
35 | );
36 |
37 | @Test
38 | void testRegex() {
39 | // given
40 | JazonMap expected = new JazonMap()
41 | .with("first", (String s) -> s.matches("bl.*"))
42 | .with("second", s -> ((String) s).matches("bl.*"))
43 | .with("third", "red");
44 | String actualJson = "{" +
45 | " \"first\": \"blue\"," +
46 | " \"second\": \"black\"," +
47 | " \"third\": \"red\"" +
48 | "}";
49 |
50 | // when
51 | MatchResult matchResult = match(expected, actualJson);
52 |
53 | // then
54 | assertTrue(matchResult.ok());
55 | }
56 |
57 | @Test
58 | void testRegexTypeMismatch() {
59 | // given
60 | JazonMap expected = new JazonMap()
61 | .with("first", (String s) -> s.matches("bl.*"));
62 | String actualJson = "{" +
63 | " \"first\": 55" +
64 | "}";
65 |
66 | // when
67 | MatchResult matchResult = match(expected, actualJson);
68 |
69 | // then
70 | assertFalse(matchResult.ok());
71 | assertEquals(matchResult.mismatch().expectationMismatch().getClass(), PredicateExecutionFailedMismatch.class);
72 | assertTrue(
73 | matchResult.mismatch().expectationMismatch().message().startsWith(
74 | "Exception occurred on predicate evaluation: \n\njava.lang.ClassCastException"
75 | )
76 | );
77 | assertEquals(matchResult.mismatch().path(), "$.first");
78 | }
79 |
80 | @Test
81 | void testPredicatedWithDeeplyNestedException() {
82 | // given
83 | JazonMap expected = new JazonMap()
84 | .with("first", this::complexOperation);
85 | String actualJson = "{" +
86 | " \"first\": 55" +
87 | "}";
88 |
89 | // when
90 | MatchResult matchResult = match(expected, actualJson);
91 |
92 | // then
93 | assertFalse(matchResult.ok());
94 | assertEquals(matchResult.mismatch().expectationMismatch().getClass(), PredicateExecutionFailedMismatch.class);
95 | assertTrue(
96 | matchResult.mismatch().expectationMismatch().message().startsWith(
97 | "Exception occurred on predicate evaluation: \n\n" +
98 | "java.lang.RuntimeException: an intentional exception for value 55"
99 | )
100 | );
101 | assertEquals(matchResult.mismatch().path(), "$.first");
102 | }
103 |
104 | @Test
105 | void testAnyNumberOfExpectationSuccess() {
106 | // given
107 | JazonMap expected = new JazonMap()
108 | .with("animals", anyNumberOf("cat"));
109 | String actualJson = "{" +
110 | " \"animals\": [\"cat\", \"cat\", \"cat\", \"cat\", \"cat\", \"cat\", \"cat\"]" +
111 | "}";
112 |
113 | // when
114 | MatchResult matchResult = match(expected, actualJson);
115 |
116 | // then
117 | assertTrue(matchResult.ok());
118 | }
119 |
120 | @Test
121 | void testAnyNumberOfExpectationFailure() {
122 | // given
123 | JazonMap expected = new JazonMap()
124 | .with("animals", anyNumberOf("cat"));
125 | String actualJson = "{" +
126 | " \"animals\": [\"cat\", \"cat\", \"dog\", \"cat\", \"cat\", \"cat\"]" +
127 | "}";
128 |
129 | // when
130 | MatchResult matchResult = match(expected, actualJson);
131 |
132 | // then
133 | assertFalse(matchResult.ok());
134 | assertEquals(matchResult.mismatch().expectationMismatch().getClass(), PrimitiveValueMismatch.class);
135 | assertEquals(matchResult.mismatch().path(), "$.animals.2");
136 | }
137 |
138 | private boolean complexOperation(Integer number) {
139 | return failingOperation(number);
140 | }
141 |
142 | private boolean failingOperation(int number) {
143 | throw new RuntimeException("an intentional exception for value " + number);
144 | }
145 |
146 | private MatchResult match(JazonMap expected, String actualJson) {
147 | return matcherFactory.matcher()
148 | .expected(expected.map())
149 | .actual(actualJson)
150 | .match();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/jazon-spock/README.md:
--------------------------------------------------------------------------------
1 | # Jazon Spock
2 | A library for test assertions on JSON payloads - for Spock framework.
3 |
4 | ## Quickstart
5 |
6 | #### Example 1: Simple exact-match
7 |
8 | ```groovy
9 | def "simple assertion passes"() {
10 | when:
11 | def response = '''
12 | {
13 | "firstname": "Steve",
14 | "lastname": "Jobs"
15 | }
16 | '''
17 |
18 | then:
19 | jazon(response).matches([firstname: 'Steve', lastname: 'Jobs'])
20 | }
21 | ```
22 |
23 | #### Example 2: Unordered array
24 |
25 | This assertion passes even though the items in the array are in different order.
26 |
27 | ```groovy
28 | def "unordered array assertion passes"() {
29 | when:
30 | def response = '''
31 | {
32 | "id": 95478,
33 | "name": "Coca Cola",
34 | "tags": ["sprite", "pepsi", "7up", "fanta", "dr pepper"]
35 | }
36 | '''
37 |
38 | then:
39 | jazon(response).matches([
40 | id: 95478,
41 | name: 'Coca Cola',
42 | tags: ['pepsi', 'dr pepper', 'sprite', 'fanta', '7up'] as Set
43 | ])
44 | }
45 | ```
46 |
47 | #### Example 3: Custom assertions
48 |
49 | If you need, instead of exact-matching, you can define custom assertions using Closures.
50 | Here for example, we used custom assertions:
51 | * to check that a number is in given range - `{ it >= 0 }`
52 | * to check that a field matches a regex - `{ it ==~ /\d+\.\d\d/ }`
53 | * to check that a field just exists, no matter of its value - `{ it != null }`
54 |
55 | ```groovy
56 | def "custom assertions"() {
57 | when:
58 | def response = '''
59 | {
60 | "id": 95478,
61 | "name": "Coca Cola",
62 | "value": "133.30",
63 | "updated_at": "1990-06-19T12:19:10Z"
64 | }
65 | '''
66 |
67 | then:
68 | jazon(response).matches([
69 | id: { it >= 0 },
70 | name: 'Coca Cola',
71 | value: { it ==~ /\d+\.\d\d/ },
72 | updated_at: { it != null }
73 | ])
74 | }
75 | ```
76 |
77 | #### Example 4: Utils extraction
78 |
79 | To avoid code duplication, you can extract your common wildcard-assertions to constants.
80 |
81 | ```groovy
82 | def "utils extraction"() {
83 | when:
84 | def response = '''
85 | {
86 | "id": 95478,
87 | "name": "Coca Cola",
88 | "value": "133.30",
89 | "updated_at": "1990-06-19T12:19:10Z"
90 | }
91 | '''
92 |
93 | then:
94 | jazon(response).matches([
95 | id: ANY_ID, // a constant
96 | name: 'Coca Cola',
97 | value: '133.30',
98 | updated_at: ANY_ISO_DATETIME // a constant
99 | ])
100 | }
101 | ```
102 |
103 | #### Example 5: Utils extraction to domain objects
104 |
105 | To avoid code duplication even more, you can extract the parts of JSON. This will also
106 | make your tests more readable.
107 | Here in the examples we extract `deal()` - a business object from sales domain.
108 |
109 | ```groovy
110 | jazon(response).matches deal(name: 'Coca Cola', value: '10.00')
111 | ```
112 |
113 | ```groovy
114 | jazon(response).matches([
115 | deal(name: 'Coca Cola', value: '10.00'),
116 | deal(name: 'Pepsi', value: '9.00'),
117 | deal(name: 'Fanta', value: '10.00'),
118 | deal(name: 'Sprite', value: '10.00'),
119 | deal(name: 'Dr Pepper', value: '12.00')
120 | ])
121 | ```
122 |
123 | ```groovy
124 | private Map deal(Map kwargs) {
125 | Map defaults = [
126 | id: ANY_ID,
127 | updated_at: ANY_ISO_DATETIME
128 | ]
129 | defaults + kwargs
130 | }
131 | ```
132 |
133 | ## Copyright and license
134 | Copyright 2019 Zendesk, Inc.
135 |
136 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
137 | You may obtain a copy of the License at
138 | http://www.apache.org/licenses/LICENSE-2.0
139 |
140 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
141 |
142 |
--------------------------------------------------------------------------------
/jazon-spock/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | name = 'Jazon Spock Adapter'
3 | description = 'A library for test assertions on JSON payloads - for Spock framework.'
4 | }
5 |
6 | apply from: '../gradle/publishing.gradle'
7 |
8 | dependencies {
9 | compile project(':jazon-core')
10 | compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.12'
11 |
12 | testCompile group: 'org.spockframework', name: 'spock-core', version: '1.2-groovy-2.4'
13 | }
14 |
--------------------------------------------------------------------------------
/jazon-spock/src/main/groovy/com/zendesk/jazon/spock/JazonSpockAdapter.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.spock
2 |
3 | import com.zendesk.jazon.MatchResult
4 | import com.zendesk.jazon.MatcherFactory
5 | import com.zendesk.jazon.actual.factory.GsonActualFactory
6 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
7 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
8 | import com.zendesk.jazon.expectation.translator.SpockTranslators
9 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
10 | import com.zendesk.jazon.expectation.translator.TranslatorMapping
11 |
12 | class JazonSpockAdapter {
13 | private static final MatcherFactory MATCHER_FACTORY = new MatcherFactory(
14 | new TranslatorFacade(translators()),
15 | new GsonActualFactory()
16 | )
17 | private final String json
18 |
19 | private JazonSpockAdapter(String json) {
20 | this.json = json
21 | }
22 |
23 | static JazonSpockAdapter jazon(String json) {
24 | return new JazonSpockAdapter(json)
25 | }
26 |
27 | boolean matches(Map jsonAsMap) {
28 | return match(jsonAsMap)
29 | }
30 |
31 | boolean matches(List jsonAsList) {
32 | return match(jsonAsList)
33 | }
34 |
35 | private boolean match(Object expected) {
36 | MatchResult matchResult = MATCHER_FACTORY.matcher()
37 | .expected(expected)
38 | .actual(json)
39 | .match()
40 | if (matchResult.ok()) {
41 | return true
42 | }
43 | throw new AssertionError(errorMessage(matchResult))
44 | }
45 |
46 | private static GString errorMessage(MatchResult matchResult) {
47 | "\n-----------------------------------\nJSON MISMATCH:\n${matchResult.message()}\n-----------------------------------\n"
48 | }
49 |
50 | private static List translators() {
51 | DefaultTranslators.translators() + JazonTypesTranslators.translators() + SpockTranslators.translators()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/jazon-spock/src/main/java/com/zendesk/jazon/expectation/translator/SpockTranslators.java:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.expectation.translator;
2 |
3 | import com.zendesk.jazon.actual.ActualJsonString;
4 | import com.zendesk.jazon.expectation.JsonExpectation;
5 | import com.zendesk.jazon.expectation.impl.PredicateExpectation;
6 | import com.zendesk.jazon.expectation.impl.PrimitiveValueExpectation;
7 | import groovy.lang.Closure;
8 | import groovy.lang.GString;
9 |
10 | import java.util.List;
11 |
12 | import static java.util.Arrays.asList;
13 |
14 | public class SpockTranslators {
15 | public static List> translators() {
16 | return asList(
17 | new TranslatorMapping<>(GString.class, new GStringTranslator()),
18 | new TranslatorMapping<>(Closure.class, new ClosureTranslator())
19 | );
20 | }
21 |
22 | private static class GStringTranslator implements Translator {
23 | @Override
24 | public JsonExpectation jsonExpectation(GString gstring, TranslatorFacade translator) {
25 | return new PrimitiveValueExpectation<>(new ActualJsonString(gstring.toString()));
26 | }
27 | }
28 |
29 | @SuppressWarnings({"rawtypes", "unchecked"})
30 | private static class ClosureTranslator implements Translator {
31 | @Override
32 | public JsonExpectation jsonExpectation(Closure object, TranslatorFacade translator) {
33 | Closure booleanClosure = (Closure) object;
34 | return new PredicateExpectation(booleanClosure::call);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/jazon-spock/src/test/groovy/com/zendesk/jazon/ExampleSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 | import spock.lang.FailsWith
4 | import spock.lang.Specification
5 |
6 | import static com.zendesk.jazon.spock.JazonSpockAdapter.jazon
7 |
8 | class ExampleSpec extends Specification {
9 |
10 | def "smoke test"() {
11 | expect:
12 | jazon('{"shark": "white"}').matches([shark: 'white'])
13 | }
14 |
15 | @FailsWith(AssertionError)
16 | def "failure format test"() {
17 | expect:
18 | jazon('{"shark": "white", "raccoon": "red"}').matches([shark: 'white'])
19 | }
20 |
21 | def "predicate expectation test"() {
22 | expect:
23 | jazon('{"shark": "white"}').matches([
24 | shark: { it.startsWith('whi') }
25 | ])
26 | }
27 |
28 | def "array can be root JSON: success"() {
29 | expect:
30 | jazon('["platypus", "narwhal"]').matches(['platypus', 'narwhal'])
31 | }
32 |
33 | @FailsWith(AssertionError)
34 | def "array can be root JSON: fails"() {
35 | expect:
36 | jazon('["platypus", "narwhal"]').matches(['platypus', 'lynx'])
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/jazon-spock/src/test/groovy/com/zendesk/jazon/MatcherForGroovySpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 | import com.zendesk.jazon.actual.factory.GsonActualFactory
4 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
5 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
6 | import com.zendesk.jazon.expectation.translator.SpockTranslators
7 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
8 | import com.zendesk.jazon.mismatch.impl.PredicateMismatch
9 | import spock.lang.Specification
10 |
11 | import static com.zendesk.jazon.expectation.Expectations.anyNumberOf
12 | import static groovy.json.JsonOutput.toJson
13 |
14 | class MatcherForGroovySpec extends Specification {
15 |
16 | MatcherFactory matcherFactory = new MatcherFactory(
17 | new TranslatorFacade(
18 | DefaultTranslators.translators() + JazonTypesTranslators.translators() + SpockTranslators.translators()
19 | ),
20 | new GsonActualFactory()
21 | )
22 |
23 | def "predicate expectation: succeeds"() {
24 | given:
25 | Closure closure = { it ==~ "dig.*" }
26 |
27 | when:
28 | def result = matcherFactory.matcher()
29 | .expected([a: closure])
30 | .actual(toJson([a: stringToMatch]))
31 | .match()
32 |
33 | then:
34 | result.ok()
35 |
36 | where:
37 | stringToMatch << [
38 | 'dig',
39 | 'digger',
40 | 'digging',
41 | 'digs',
42 | ]
43 | }
44 |
45 | def "predicate expectation: fails"() {
46 | given:
47 | Closure closure = { it ==~ "dig.*" }
48 |
49 | when:
50 | def result = matcherFactory.matcher()
51 | .expected([a: closure])
52 | .actual(toJson([a: stringToMatch]))
53 | .match()
54 |
55 | then:
56 | !result.ok()
57 | result.mismatch().expectationMismatch() == PredicateMismatch.INSTANCE
58 | result.mismatch().path() == '$.a'
59 |
60 | where:
61 | stringToMatch << [
62 | 'dog',
63 | 'di',
64 | 'do',
65 | 'dagger',
66 | 'refrigerator',
67 | ]
68 | }
69 |
70 | def "predicate expectation can be root"() {
71 | when:
72 | def result = matcherFactory.matcher()
73 | .expected({ it ==~ 'dig.*' })
74 | .actual('refrigerator')
75 | .match()
76 |
77 | then:
78 | !result.ok()
79 | result.mismatch().expectationMismatch() == PredicateMismatch.INSTANCE
80 | result.mismatch().path() == '$'
81 | }
82 |
83 | def "Groovy's GString works well"() {
84 | given:
85 | def someVariable = 123
86 | def gstring = "this is interpolated string with $someVariable"
87 |
88 | when:
89 | def result = matcherFactory.matcher()
90 | .expected([a: gstring])
91 | .actual(toJson([a: 'this is interpolated string with 123']))
92 | .match()
93 |
94 | then:
95 | result.ok()
96 | }
97 |
98 | def "predicate expectation for array: fails"() {
99 | when:
100 | def result = matcherFactory.matcher()
101 | .expected([a: predicate])
102 | .actual(toJson([a: ['chips', 'ketchup', 'fish']]))
103 | .match()
104 |
105 | then:
106 | !result.ok()
107 | result.mismatch().expectationMismatch() == PredicateMismatch.INSTANCE
108 | result.mismatch().path() == '$.a'
109 |
110 | where:
111 | predicate << [
112 | { it.size() == 9 },
113 | { it[1] == 'fish' },
114 | ]
115 | }
116 |
117 | def "predicate expectation for array succeeds"() {
118 | when:
119 | def result = matcherFactory.matcher()
120 | .expected([a: predicate])
121 | .actual(toJson([a: ['chips', 'ketchup', 'fish']]))
122 | .match()
123 |
124 | then:
125 | result.ok()
126 |
127 | where:
128 | predicate << [
129 | { it.size() == 3 },
130 | { it[1] == 'ketchup' },
131 | ]
132 | }
133 |
134 | def "predicate expectation for object: fails"() {
135 | when:
136 | def result = matcherFactory.matcher()
137 | .expected([a: predicate])
138 | .actual(toJson([a: [name: 'tomato', color: 'red']]))
139 | .match()
140 |
141 | then:
142 | !result.ok()
143 | result.mismatch().expectationMismatch() == PredicateMismatch.INSTANCE
144 | result.mismatch().path() == '$.a'
145 |
146 | where:
147 | predicate << [
148 | { it.size() == 17 },
149 | { it.name == 'cucumber' },
150 | ]
151 | }
152 |
153 | def "predicate expectation for object succeeds"() {
154 | when:
155 | def result = matcherFactory.matcher()
156 | .expected([a: predicate])
157 | .actual(toJson([a: [name: 'tomato', color: 'red']]))
158 | .match()
159 |
160 | then:
161 | result.ok()
162 |
163 | where:
164 | predicate << [
165 | { it.name == 'tomato' },
166 | { it.color == 'red' },
167 | ]
168 | }
169 |
170 | def "array each element expectation works correctly with lambda-style expectation"() {
171 | when:
172 | def result = matcherFactory.matcher()
173 | .expected([a: anyNumberOf {it -> it > 5}])
174 | .actual(toJson([a: [6, 7, 8, 9, 0]]))
175 | .match()
176 |
177 | then:
178 | !result.ok()
179 | result.mismatch().expectationMismatch() == PredicateMismatch.INSTANCE
180 | result.mismatch().path() == '$.a.4'
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/jazon-spock/src/test/groovy/com/zendesk/jazon/MessagesForGroovySpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon
2 |
3 |
4 | import com.zendesk.jazon.actual.factory.GsonActualFactory
5 | import com.zendesk.jazon.expectation.translator.DefaultTranslators
6 | import com.zendesk.jazon.expectation.translator.JazonTypesTranslators
7 | import com.zendesk.jazon.expectation.translator.SpockTranslators
8 | import com.zendesk.jazon.expectation.translator.TranslatorFacade
9 | import spock.lang.Specification
10 |
11 | import static groovy.json.JsonOutput.toJson
12 |
13 | class MessagesForGroovySpec extends Specification {
14 |
15 | MatcherFactory matcherFactory = new MatcherFactory(
16 | new TranslatorFacade(
17 | DefaultTranslators.translators() + JazonTypesTranslators.translators() + SpockTranslators.translators()
18 | ),
19 | new GsonActualFactory()
20 | )
21 |
22 | def "predicate expectation: fails"() {
23 | given:
24 | Closure closure = { it ==~ "dig.*" }
25 |
26 | when:
27 | def result = matcherFactory.matcher()
28 | .expected([a: closure])
29 | .actual(toJson([a: 'rosemary']))
30 | .match()
31 |
32 | then:
33 | result.message() == 'Mismatch at path: $.a\nCustom predicate does not match the value.'
34 | println result.message()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/jazon-spock/src/test/groovy/com/zendesk/jazon/spock/JazonSpockAdapterSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.zendesk.jazon.spock
2 |
3 |
4 | import spock.lang.Specification
5 |
6 | import static com.zendesk.jazon.spock.JazonSpockAdapter.jazon
7 |
8 | class JazonSpockAdapterSpec extends Specification {
9 |
10 | def 'formats the error message well'() {
11 | when:
12 | jazon('["platypus", "narwhal"]')
13 | .matches(['platypus', 'penguin'])
14 |
15 | then:
16 | AssertionError assertionError = thrown()
17 | assertionError.toString() == EXPECTED_ERROR_MESSAGE
18 | }
19 |
20 | def 'smoke test for all types at once'() {
21 | expect:
22 | jazon(JSON_WITH_ALL_TYPES).matches([
23 | nested_object: [
24 | some_number: 123
25 | ],
26 | nested_array: ['red', 'green', 'blue'],
27 | some_boolean: true,
28 | a_null: null,
29 | some_string: 'whatever',
30 | some_integer: 123,
31 | some_long: 123456789012345678,
32 | some_double: 99.77
33 | ])
34 | }
35 |
36 | private static final String EXPECTED_ERROR_MESSAGE = '''java.lang.AssertionError:
37 | -----------------------------------
38 | JSON MISMATCH:
39 | Mismatch at path: $.1
40 | Expected: "penguin"
41 | Actual: "narwhal"
42 | -----------------------------------
43 | '''
44 | private static final String JSON_WITH_ALL_TYPES = '''
45 | {
46 | "nested_object": {
47 | "some_number": 123
48 | },
49 | "nested_array": ["red", "green", "blue"],
50 | "some_boolean": true,
51 | "a_null": null,
52 | "some_string": "whatever",
53 | "some_integer": 123,
54 | "some_long": 123456789012345678,
55 | "some_double": 99.77
56 | }
57 | '''
58 | }
59 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'jazon'
2 | include 'jazon-core'
3 | include 'jazon-spock'
4 | include 'jazon-junit'
5 | include 'examples'
6 |
7 |
--------------------------------------------------------------------------------