├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── catalog-info.yaml ├── future ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── hamcrest │ │ └── future │ │ ├── CompletableFutureMatchers.java │ │ ├── ExceptionallyCompletedBlockingCompletionStage.java │ │ ├── ExceptionallyCompletedBlockingFuture.java │ │ ├── ExceptionallyCompletedCompletionStage.java │ │ ├── ExceptionallyCompletedFuture.java │ │ ├── FutureMatchers.java │ │ ├── SuccessfullyCompletedBlockingCompletionStage.java │ │ ├── SuccessfullyCompletedBlockingFuture.java │ │ ├── SuccessfullyCompletedCompletionStage.java │ │ ├── SuccessfullyCompletedFuture.java │ │ └── Utils.java │ └── test │ └── java │ └── com │ └── spotify │ └── hamcrest │ └── future │ ├── CompletableFutureMatchersTest.java │ ├── ExceptionallyCompletedBlockingCompletionStageTest.java │ ├── ExceptionallyCompletedBlockingFutureTest.java │ ├── ExceptionallyCompletedCompletionStageTest.java │ ├── ExceptionallyCompletedFutureTest.java │ ├── FutureMatchersTest.java │ ├── SuccessfullyCompletedBlockingCompletionStageTest.java │ ├── SuccessfullyCompletedBlockingFutureTest.java │ ├── SuccessfullyCompletedCompletionStageTest.java │ ├── SuccessfullyCompletedFutureTest.java │ └── TestUtils.java ├── jackson ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── hamcrest │ │ └── jackson │ │ ├── AbstractJsonNodeMatcher.java │ │ ├── IsJsonArray.java │ │ ├── IsJsonBoolean.java │ │ ├── IsJsonMissing.java │ │ ├── IsJsonNull.java │ │ ├── IsJsonNumber.java │ │ ├── IsJsonObject.java │ │ ├── IsJsonStringMatching.java │ │ ├── IsJsonText.java │ │ └── JsonMatchers.java │ └── test │ └── java │ └── com │ └── spotify │ └── hamcrest │ └── jackson │ ├── IsJsonArrayTest.java │ ├── IsJsonBooleanTest.java │ ├── IsJsonMissingTest.java │ ├── IsJsonNullTest.java │ ├── IsJsonNumberTest.java │ ├── IsJsonObjectTest.java │ ├── IsJsonStringMatchingTest.java │ └── IsJsonTextTest.java ├── optional ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── hamcrest │ │ └── optional │ │ ├── EmptyOptional.java │ │ ├── OptionalMatchers.java │ │ └── PresentOptional.java │ └── test │ └── java │ └── com │ └── spotify │ └── hamcrest │ └── optional │ ├── EmptyOptionalTest.java │ ├── OptionalMatchersTest.java │ └── PresentOptionalTest.java ├── pojo ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── spotify │ │ └── hamcrest │ │ └── pojo │ │ ├── IsPojo.java │ │ └── MethodReference.java │ └── test │ └── java │ └── com │ └── spotify │ └── hamcrest │ └── pojo │ ├── IsPojoTest.java │ ├── SomeClass.java │ └── SomeParent.java ├── pom.xml └── util ├── pom.xml └── src ├── main └── java │ └── com │ └── spotify │ └── hamcrest │ └── util │ ├── DescriptionUtils.java │ └── LanguageUtils.java └── test └── java └── com └── spotify └── hamcrest └── util ├── DescriptionUtilsTest.java └── LanguageUtilsTest.java /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Build/Test 14 | 15 | runs-on: ubuntu-latest 16 | 17 | # Test on each supported Java LTS version 18 | strategy: 19 | matrix: 20 | java_version: [8, 11, 17] 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 2 25 | - name: Set up JDK ${{ matrix.java_version }} 26 | uses: actions/setup-java@v2 27 | with: 28 | distribution: 'zulu' 29 | java-version: ${{ matrix.java_version }} 30 | - name: Cache Maven packages 31 | uses: actions/cache@v2 32 | with: 33 | path: ~/.m2 34 | key: ${{ runner.os }}-java-${{ matrix.java_version }}-m2-${{ hashFiles('**/pom.xml') }} 35 | restore-keys: ${{ runner.os }}-java-${{ matrix.java_version }}-m2 36 | - name: Build with Maven 37 | run: mvn --batch-mode --update-snapshots verify 38 | - name: Codecov 39 | uses: codecov/codecov-action@v1 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.git* 2 | target/ 3 | .idea/ 4 | *.iml 5 | release.properties 6 | *.releaseBackup 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotify Hamcrest 2 | 3 | [![Build Status](https://github.com/spotify/java-hamcrest/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/spotify/java-hamcrest/actions/workflows/ci.yaml) 4 | [![codecov](https://codecov.io/gh/spotify/java-hamcrest/branch/master/graph/badge.svg)](https://codecov.io/gh/spotify/java-hamcrest) 5 | [![Maven Central](https://img.shields.io/maven-central/v/com.spotify/hamcrest.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.spotify%22%20hamcrest*) 6 | [![License](https://img.shields.io/github/license/spotify/java-hamcrest.svg)](LICENSE) 7 | 8 | This is a collection of libraries extending the Hamcrest matching 9 | library with useful matchers. We consider this library in beta but use it 10 | in many of our internal projects. 11 | 12 | - [Spotify Hamcrest](#spotify-hamcrest) 13 | - [Download](#download) 14 | - [Getting Started](#getting-started) 15 | - [POJO matchers](#pojo-matchers) 16 | - [JSON matchers](#json-matchers) 17 | - [java.util.Optional matchers](#javautiloptional-matchers) 18 | - [Future matchers](#future-matchers) 19 | - [raw `Future` matchers](#raw-future-matchers) 20 | - [Java 8's `CompletableFuture` matchers](#java-8s-completablefuture-matchers) 21 | - [Prerequisities](#prerequisities) 22 | - [Releasing](#releasing) 23 | - [Code of conduct](#code-of-conduct) 24 | - [Ownership](#ownership) 25 | 26 | 27 | ## Download 28 | 29 | Each of these modules is published to [Maven Central][maven-search] with the groupId of `com.spotify`. 30 | The list of modules available is: 31 | 32 | * hamcrest-pojo 33 | * hamcrest-jackson 34 | * hamcrest-optional 35 | * hamcrest-future 36 | 37 | 38 | ## Getting Started 39 | 40 | ### POJO matchers 41 | [![Javadocs](http://www.javadoc.io/badge/com.spotify/hamcrest-pojo.svg?color=blue)](http://www.javadoc.io/doc/com.spotify/hamcrest-pojo) 42 | 43 | Many applications at Spotify are very data heavy. They might be 44 | aggregation services that combine a lot of data structures into even 45 | more complicated data structures. And the basic data structures are 46 | usually complicated to begin with. 47 | 48 | The POJO matcher library lets you describe the structure of a POJO in 49 | a fluent style and then match against that structure. It's optimized 50 | for very complicated objects with a lot of properties. When a 51 | mismatch occurs, the library tries to minimally describe the mismatch. 52 | 53 | Example: 54 | 55 | ```java 56 | final List users; 57 | try (Stream userStream = sut.fetchAllUsers()) { 58 | users = userStream.collect(Collectors.toList()); 59 | } 60 | 61 | assertThat(users, contains( 62 | pojo(User.class) 63 | .where("address", is( 64 | pojo(Address.class) 65 | .withProperty("street", is("Main Street")) 66 | .withProperty("country", is("US")) 67 | )) 68 | .where("product", is( 69 | pojo(Product.class) 70 | .withProperty("id", is(1)) 71 | .withProperty("name", is("premium")) 72 | .withProperty("metadata", is("{\"foo\": [\"bar\", \"baz\"]}")) 73 | .withProperty("creationDate", is(Timestamp.from(Instant.EPOCH))) 74 | .withProperty("isTest", is(false)) 75 | )) 76 | )); 77 | ``` 78 | 79 | Example output: 80 | 81 | ``` 82 | Expected: 83 | iterable containing [User { 84 | address(): is Address { 85 | getStreet(): is "Main Street" 86 | getCountry(): is "US" 87 | } 88 | product(): is Product { 89 | getId(): is <1> 90 | getName(): is "premium" 91 | getMetadata(): is "{\"foo\": [\"bar\", \"baz\"]}" 92 | getCreationDate(): is <1970-01-01 01:00:00.0> 93 | getIsTest(): is 94 | } 95 | }] 96 | 97 | but: 98 | item 0: User { 99 | ... 100 | product(): Product { 101 | ... 102 | getMetadata(): was "{\"foo\": \"bar\"}" 103 | ... 104 | } 105 | ... 106 | } 107 | ``` 108 | 109 | ### JSON matchers 110 | [![Javadocs](http://www.javadoc.io/badge/com.spotify/hamcrest-jackson.svg?color=blue)](http://www.javadoc.io/doc/com.spotify/hamcrest-jackson) 111 | 112 | Similar to the POJO matchers, the JSON matchers let you describe a 113 | JSON structure and match against it. 114 | 115 | The match can be on a `String` or a jackson `JsonNode`. 116 | 117 | ```java 118 | // You can match a String 119 | String jsonString = "{" + 120 | " \"foo\": 1," + 121 | " \"bar\": true," + 122 | " \"baz\": {" + 123 | " \"foo\": true" + 124 | " }" + 125 | "}" 126 | 127 | // You can match a Json String directly 128 | assertThat("{}", isJsonStringMatching(jsonObject())); 129 | 130 | // Or match a Jackson node 131 | JsonNode json = new ObjectMapper().readTree(jsonString); 132 | assertThat(json, is( 133 | jsonObject() 134 | .where("foo", is(jsonInt(1))) 135 | .where("bar", is(jsonBoolean(true))) 136 | .where("baz", is( 137 | jsonObject() 138 | .where("foo", is(jsonNull())))))); 139 | ``` 140 | 141 | A failing test would look like: 142 | 143 | ``` 144 | Expected: 145 | { 146 | "foo": is a number node is <1> 147 | "bar": is a boolean node is 148 | "baz": is { 149 | "foo": is a null node 150 | } 151 | } 152 | 153 | but: 154 | { 155 | ... 156 | "baz": { 157 | ... 158 | "foo": was not a null node, but a boolean node 159 | ... 160 | } 161 | ... 162 | } 163 | ``` 164 | 165 | You can match a JSON Array by combining with existing Hamcrest collection Matchers: 166 | 167 | ```java 168 | String jsonArrayString = "["foo", "bar"]"; 169 | JsonNode json = new ObjectMapper().readTree(jsonArrayString); 170 | assertThat(json, is(jsonArray(contains(jsonText("foo"), jsonText("bar"))))); 171 | ``` 172 | 173 | ### java.util.Optional matchers 174 | [![Javadocs](http://www.javadoc.io/badge/com.spotify/hamcrest-optional.svg?color=blue)](http://www.javadoc.io/doc/com.spotify/hamcrest-optional) 175 | 176 | `com.spotify:hamcrest-optional` provides matchers for the Java 8 177 | Optional type so you don't have to unpack the Optional in your tests. 178 | 179 | ```java 180 | final Optional response = methodUnderTest(); 181 | assertThat(response, is(optionalWithValue(equalTo("foo"))); 182 | 183 | final Optional> col = anotherMethod(); 184 | assertThat(response, is(optionalWithValue(containsInAnyOrder(...)))); 185 | 186 | // or if you only care that the Optional is non-empty: 187 | assertThat(response, is(optionalWithValue())); 188 | 189 | // or if you expect an empty Optional: 190 | assertThat(response, is(emptyOptional())); 191 | ``` 192 | 193 | ### Future matchers 194 | [![Javadocs](http://www.javadoc.io/badge/com.spotify/hamcrest-future.svg?color=blue)](http://www.javadoc.io/doc/com.spotify/hamcrest-future) 195 | 196 | Similar to the Optional matchers, the CompletionStage / 197 | CompletableFuture matchers in `com.spotify:hamcrest-future` allow 198 | you to assert against the value or completion state of a 199 | CompletionStage without having to unpack it in your test code or 200 | handle the checked exceptions of `Future.get()` (or using 201 | `Futures.getUnchecked(future)`). 202 | 203 | There are four dimensions you can choose from when using Future 204 | matchers: 205 | 206 | * raw `Future` vs Java 8's `CompletableFuture` 207 | * blocking vs non-blocking 208 | * blocking: the matcher will wait, perhaps indefinitely, for the 209 | future to complete 210 | * non-blocking: the matcher will not match if the future is not 211 | yet completed 212 | * completed successfully vs completed with an exception 213 | * match anything vs pass in another matcher 214 | 215 | So there are a total of 16 methods you can call: 216 | 217 | #### raw `Future` matchers 218 | 219 | Use `com.spotify.hamcrest.future.FutureMatchers`: 220 | 221 | | | blocking | non-blocking | 222 | | ----------- | --------------------------------------- | ------------ | 223 | | successful | futureWillCompleteWithValue\[That]() | futureCompletedWithValue\[That]() | 224 | | exceptional | futureWillCompleteWithException\[That]() | futureCompletedWithException\[That]() | 225 | 226 | #### Java 8's `CompletableFuture` matchers 227 | 228 | Use `com.spotify.hamcrest.future.CompletableFutureMatchers`: 229 | 230 | | | blocking | non-blocking | 231 | | ----------- | -------------------------------------- | ------------ | 232 | | successful | stageWillCompleteWithValue\[That]() | stageCompletedWithValue\[That]() | 233 | | exceptional | stageWillCompleteWithException\[That]() | stageCompletedWithException\[That]() | 234 | 235 | Note that to test that a CompletionStage completed with a certain 236 | value or exception use `..That(..)`. 237 | 238 | ```java 239 | CompletionStage> f = someMethod(); 240 | assertThat(f, stageCompletedWithValueThat(contains(...)); 241 | 242 | CompletionStage c = methodThatShouldFail(); 243 | assertThat(c, stageCompletedWithExceptionThat(isA(FooException.class))); 244 | ``` 245 | 246 | If you want the matcher to block until the CompletionStage is 247 | completed, use `stageWillCompleteWithValueThat(..)`: 248 | 249 | ```java 250 | // warning: might block forever if the stage never completes! 251 | CompletionStage> f = someMethod(); 252 | assertThat(f, stageWillCompleteWithValueThat(is(equalTo(...))); 253 | ``` 254 | 255 | Be careful when using this matcher as it might block forever if the 256 | stage never completes! Consider restructuring your tests so that the 257 | completions returned from the method/class being tested are 258 | immediately completed (e.g. using MoreExecutors.directExecutor, etc). 259 | 260 | 261 | ## Prerequisities 262 | 263 | Any platform that has the following 264 | 265 | * Java 8+ 266 | * Maven 3 (for compiling) 267 | 268 | 269 | ## Releasing 270 | 271 | This plugin is uploaded to Maven Central via the Maven release plugin. You'll need 272 | credentials for Spotify's Sonatype account in your `~/.m2/settings.xml` as well as a GPG key to 273 | sign the artifacts. 274 | 275 | ``` 276 | mvn -Dgpg.keyname= release:prepare release:perform 277 | ``` 278 | 279 | 280 | ## Code of conduct 281 | 282 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are 283 | expected to honor this code. 284 | 285 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 286 | [maven-search]: https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.spotify%22%20hamcrest* 287 | 288 | ## Ownership 289 | 290 | The Weaver squad is currently owning this project internally. 291 | We are currently in the evaluating process of the ownership of this and other OSS Java libraries. 292 | The ownership takes into account **ONLY** security maintenance. 293 | 294 | This repo is also co-owned by other people: 295 | 296 | * [mattnworb](https://github.com/mattnworb) -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Resource 3 | metadata: 4 | name: java-hamcrest 5 | spec: 6 | type: library 7 | owner: weaver 8 | -------------------------------------------------------------------------------- /future/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hamcrest 5 | com.spotify 6 | 1.3.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hamcrest-future 11 | 12 | 13 | 14 | org.hamcrest 15 | hamcrest 16 | 17 | 18 | junit 19 | junit 20 | test 21 | 22 | 23 | com.google.guava 24 | guava 25 | test 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/CompletableFutureMatchers.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static org.hamcrest.CoreMatchers.any; 24 | import static org.hamcrest.CoreMatchers.anything; 25 | import static org.hamcrest.CoreMatchers.is; 26 | 27 | import java.util.concurrent.CompletionStage; 28 | import org.hamcrest.Matcher; 29 | 30 | /** 31 | * Matchers for {@link java.util.concurrent.CompletionStage} instances. 32 | * 33 | *

See also {@link FutureMatchers} for similar matchers against Future instances - because the 34 | * {@link CompletionStage} interface does not extend the Future interface, we need separate method 35 | * definitions. 36 | */ 37 | public final class CompletableFutureMatchers { 38 | 39 | private CompletableFutureMatchers() {} 40 | 41 | /** 42 | * Creates a {@link Matcher} that matches a {@link CompletionStage} that has completed with an 43 | * exception. A {@link CompletionStage} that is not yet completed will not be matched. 44 | */ 45 | public static Matcher> stageCompletedWithException() { 46 | return stageCompletedWithExceptionThat(is(any(Throwable.class))); 47 | } 48 | 49 | /** 50 | * Creates a {@link Matcher} that matches a {@link CompletionStage} that has completed with an 51 | * exception that matches the given Matcher. A {@link CompletionStage} that is not yet completed 52 | * will not be matched. 53 | */ 54 | public static Matcher> stageCompletedWithExceptionThat( 55 | final Matcher matcher) { 56 | return new ExceptionallyCompletedCompletionStage(matcher); 57 | } 58 | 59 | /** 60 | * Creates a {@link Matcher} that matches a {@link CompletionStage} that has completed with a 61 | * value. A {@link CompletionStage} that is not yet completed will not be matched. 62 | */ 63 | public static Matcher> stageCompletedWithValue() { 64 | return stageCompletedWithValueThat(anything()); 65 | } 66 | 67 | /** 68 | * Creates a {@link Matcher} that matches a {@link CompletionStage} that has completed with a 69 | * value that matches a given Matcher. A {@link CompletionStage} that is not yet completed will 70 | * not be matched. 71 | */ 72 | public static Matcher> stageCompletedWithValueThat( 73 | final Matcher matcher) { 74 | return new SuccessfullyCompletedCompletionStage<>(matcher); 75 | } 76 | 77 | /** 78 | * Creates a {@link Matcher} that matches when the {@link CompletionStage} completes with a value. 79 | * 80 | *

If the {@link CompletionStage} has not yet completed, this matcher waits for it to 81 | * finish. 82 | */ 83 | public static Matcher> stageWillCompleteWithValue() { 84 | return stageWillCompleteWithValueThat(anything()); 85 | } 86 | 87 | /** 88 | * Creates a {@link Matcher} that matches when the {@link CompletionStage} completes with a value 89 | * that matches the given Matcher. 90 | * 91 | *

If the {@link CompletionStage} has not yet completed, this matcher waits for it to 92 | * finish. 93 | */ 94 | public static Matcher> stageWillCompleteWithValueThat( 95 | final Matcher matcher) { 96 | return new SuccessfullyCompletedBlockingCompletionStage<>(matcher); 97 | } 98 | 99 | /** 100 | * Creates a {@link Matcher} that matches when the {@link CompletionStage} completes with an 101 | * exception. 102 | * 103 | *

If the {@link CompletionStage} has not yet completed, this matcher waits for it to 104 | * finish. 105 | */ 106 | public static Matcher> stageWillCompleteWithException() { 107 | return stageWillCompleteWithExceptionThat(is(any(Throwable.class))); 108 | } 109 | 110 | /** 111 | * Creates a {@link Matcher} that matches when the {@link CompletionStage} completes with an 112 | * exception. 113 | * 114 | *

If the {@link CompletionStage} has not yet completed, this matcher waits for it to 115 | * finish. 116 | */ 117 | public static Matcher> stageWillCompleteWithExceptionThat( 118 | final Matcher matcher) { 119 | return new ExceptionallyCompletedBlockingCompletionStage(matcher); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/ExceptionallyCompletedBlockingCompletionStage.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.CancellationException; 25 | import java.util.concurrent.CompletionStage; 26 | import java.util.concurrent.ExecutionException; 27 | import org.hamcrest.Description; 28 | import org.hamcrest.Matcher; 29 | import org.hamcrest.TypeSafeDiagnosingMatcher; 30 | 31 | /** 32 | * Creates a Matcher that matches a CompletionStage that has completed with an exception that 33 | * matches the given Matcher. If the CompletionStage has not yet completed, this matcher waits for 34 | * it to finish. 35 | */ 36 | class ExceptionallyCompletedBlockingCompletionStage 37 | extends TypeSafeDiagnosingMatcher> { 38 | 39 | private final Matcher matcher; 40 | 41 | ExceptionallyCompletedBlockingCompletionStage(final Matcher matcher) { 42 | this.matcher = Objects.requireNonNull(matcher); 43 | } 44 | 45 | @Override 46 | protected boolean matchesSafely( 47 | final CompletionStage stage, final Description mismatchDescription) { 48 | try { 49 | final Object item = stage.toCompletableFuture().get(); 50 | mismatchDescription 51 | .appendText("a stage that completed with a value that was ") 52 | .appendValue(item); 53 | return false; 54 | } catch (InterruptedException e) { 55 | mismatchDescription.appendText("a stage that was interrupted"); 56 | return false; 57 | } catch (CancellationException e) { 58 | mismatchDescription.appendText("a stage that was cancelled"); 59 | return false; 60 | } catch (ExecutionException e) { 61 | if (matcher.matches(e.getCause())) { 62 | return true; 63 | } else { 64 | mismatchDescription.appendText("a stage completed exceptionally with "); 65 | matcher.describeMismatch(e.getCause(), mismatchDescription); 66 | return false; 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public void describeTo(final Description description) { 73 | description 74 | .appendText("a stage completing with an exception that ") 75 | .appendDescriptionOf(matcher); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/ExceptionallyCompletedBlockingFuture.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.CancellationException; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.concurrent.Future; 27 | import org.hamcrest.Description; 28 | import org.hamcrest.Matcher; 29 | import org.hamcrest.TypeSafeDiagnosingMatcher; 30 | 31 | class ExceptionallyCompletedBlockingFuture extends TypeSafeDiagnosingMatcher> { 32 | 33 | private final Matcher matcher; 34 | 35 | /** 36 | * Creates a new ExceptionallyCompletedBlockingFuture where the exception that the Future finished 37 | * with an exception that matches the given Matcher. 38 | */ 39 | ExceptionallyCompletedBlockingFuture(final Matcher matcher) { 40 | this.matcher = Objects.requireNonNull(matcher); 41 | } 42 | 43 | @Override 44 | protected boolean matchesSafely(final Future future, final Description mismatchDescription) { 45 | try { 46 | final T item = future.get(); 47 | mismatchDescription 48 | .appendText("a future that completed to a value that was ") 49 | .appendValue(item); 50 | return false; 51 | } catch (InterruptedException e) { 52 | mismatchDescription.appendText("a future that was interrupted"); 53 | return false; 54 | } catch (CancellationException e) { 55 | mismatchDescription.appendText("a future that was cancelled"); 56 | return false; 57 | } catch (ExecutionException e) { 58 | if (matcher.matches(e.getCause())) { 59 | return true; 60 | } else { 61 | mismatchDescription.appendText("a future completed exceptionally with "); 62 | matcher.describeMismatch(e.getCause(), mismatchDescription); 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public void describeTo(final Description description) { 70 | description 71 | .appendText("a future that completed with an exception that ") 72 | .appendDescriptionOf(matcher); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/ExceptionallyCompletedCompletionStage.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.CompletableFuture; 25 | import java.util.concurrent.CompletionException; 26 | import java.util.concurrent.CompletionStage; 27 | import org.hamcrest.Description; 28 | import org.hamcrest.Matcher; 29 | import org.hamcrest.TypeSafeDiagnosingMatcher; 30 | 31 | /** 32 | * Creates a Matcher that matches a CompletionStage that has completed with an exception that 33 | * matches the given Matcher. A CompletionStage that is not yet completed will not be matched. 34 | */ 35 | class ExceptionallyCompletedCompletionStage extends TypeSafeDiagnosingMatcher> { 36 | 37 | private final Matcher matcher; 38 | 39 | ExceptionallyCompletedCompletionStage(final Matcher matcher) { 40 | this.matcher = Objects.requireNonNull(matcher); 41 | } 42 | 43 | @Override 44 | protected boolean matchesSafely( 45 | final CompletionStage stage, final Description mismatchDescription) { 46 | final CompletableFuture future = stage.toCompletableFuture(); 47 | if (future.isDone()) { 48 | if (future.isCancelled()) { 49 | mismatchDescription.appendText("a stage that was cancelled"); 50 | return false; 51 | } else if (future.isCompletedExceptionally()) { 52 | try { 53 | future.getNow(null); 54 | throw new AssertionError( 55 | "This should never happen because the stage completed exceptionally."); 56 | } catch (CompletionException e) { 57 | if (matcher.matches(e.getCause())) { 58 | return true; 59 | } else { 60 | mismatchDescription.appendText("a stage completed exceptionally with "); 61 | matcher.describeMismatch(e.getCause(), mismatchDescription); 62 | return false; 63 | } 64 | } 65 | } else { 66 | mismatchDescription 67 | .appendText("a stage that completed to a value that was ") 68 | .appendValue(future.getNow(null)); 69 | return false; 70 | } 71 | } else { 72 | mismatchDescription.appendText("a stage that was not completed"); 73 | return false; 74 | } 75 | } 76 | 77 | @Override 78 | public void describeTo(final Description description) { 79 | description 80 | .appendText("a stage that completed with an exception that ") 81 | .appendDescriptionOf(matcher); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/ExceptionallyCompletedFuture.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.concurrent.Future; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.TypeSafeDiagnosingMatcher; 29 | 30 | class ExceptionallyCompletedFuture extends TypeSafeDiagnosingMatcher> { 31 | 32 | private final Matcher matcher; 33 | 34 | /** 35 | * Creates a new ExceptionallyCompletedFuture where the exception that the Future finished with 36 | * matches the given Matcher. 37 | */ 38 | ExceptionallyCompletedFuture(final Matcher matcher) { 39 | this.matcher = Objects.requireNonNull(matcher); 40 | } 41 | 42 | @Override 43 | protected boolean matchesSafely(final Future future, final Description mismatchDescription) { 44 | if (future.isDone()) { 45 | if (future.isCancelled()) { 46 | mismatchDescription.appendText("a future that was cancelled"); 47 | return false; 48 | } else { 49 | final T value; 50 | try { 51 | value = future.get(); 52 | } catch (ExecutionException e) { 53 | final Throwable cause = e.getCause(); 54 | if (matcher.matches(cause)) { 55 | return true; 56 | } else { 57 | mismatchDescription.appendText("a future completed exceptionally with "); 58 | matcher.describeMismatch(cause, mismatchDescription); 59 | return false; 60 | } 61 | } catch (InterruptedException e) { 62 | throw new AssertionError("This should never happen because the future is completed."); 63 | } 64 | 65 | mismatchDescription 66 | .appendText("a future that completed to a value that was ") 67 | .appendValue(value); 68 | return false; 69 | } 70 | } else { 71 | mismatchDescription.appendText("a future that was not done"); 72 | return false; 73 | } 74 | } 75 | 76 | @Override 77 | public void describeTo(final Description description) { 78 | description 79 | .appendText("a future that completed with an exception that ") 80 | .appendDescriptionOf(matcher); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/FutureMatchers.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static org.hamcrest.CoreMatchers.any; 24 | 25 | import java.util.concurrent.Future; 26 | import org.hamcrest.CoreMatchers; 27 | import org.hamcrest.Matcher; 28 | 29 | public class FutureMatchers { 30 | 31 | private FutureMatchers() {} 32 | 33 | /** 34 | * Creates a Matcher that matches a Future that has completed with an exception. A Future that is 35 | * not yet completed will not be matched. 36 | */ 37 | public static Matcher> futureCompletedWithException() { 38 | return futureCompletedWithExceptionThat(any(Throwable.class)); 39 | } 40 | 41 | /** 42 | * Creates a Matcher that matches a Future that has completed with an exception that matches the 43 | * given Matcher. A Future that is not yet completed will not be matched. 44 | */ 45 | public static Matcher> futureCompletedWithExceptionThat( 46 | final Matcher matcher) { 47 | return new ExceptionallyCompletedFuture<>(matcher); 48 | } 49 | 50 | /** 51 | * Creates a Matcher that matches a Future that has completed with a value. A Future that is not 52 | * yet completed will not be matched. 53 | */ 54 | public static Matcher> futureCompletedWithValue() { 55 | return futureCompletedWithValueThat(CoreMatchers.anything()); 56 | } 57 | 58 | /** 59 | * Creates a Matcher that matches a Future that has completed with a value that matches a given 60 | * Matcher. A Future that is not yet completed will not be matched. 61 | */ 62 | public static Matcher> futureCompletedWithValueThat( 63 | final Matcher matcher) { 64 | return new SuccessfullyCompletedFuture<>(matcher); 65 | } 66 | 67 | /** 68 | * Creates a Matcher that matches a Future that has completed with an exception. 69 | * 70 | *

If the Future has not yet completed, this matcher waits for it to finish. 71 | */ 72 | public static Matcher> futureWillCompleteWithException() { 73 | return futureWillCompleteWithExceptionThat(any(Throwable.class)); 74 | } 75 | 76 | /** 77 | * Creates a Matcher that matches a Future that has completed with an exception that matches the 78 | * given Matcher. 79 | * 80 | *

If the Future has not yet completed, this matcher waits for it to finish. 81 | */ 82 | public static Matcher> futureWillCompleteWithExceptionThat( 83 | final Matcher matcher) { 84 | return new ExceptionallyCompletedBlockingFuture<>(matcher); 85 | } 86 | 87 | /** 88 | * Creates a Matcher that matches a Future that has completed with a value. 89 | * 90 | *

If the Future has not yet completed, this matcher waits for it to finish. 91 | */ 92 | public static Matcher> futureWillCompleteWithValue() { 93 | return futureWillCompleteWithValueThat(CoreMatchers.anything()); 94 | } 95 | 96 | /** 97 | * Creates a Matcher that matches a Future that has completed with a value that matches a given 98 | * Matcher. 99 | * 100 | *

If the Future has not yet completed, this matcher waits for it to finish. 101 | */ 102 | public static Matcher> futureWillCompleteWithValueThat( 103 | final Matcher matcher) { 104 | return new SuccessfullyCompletedBlockingFuture<>(matcher); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/SuccessfullyCompletedBlockingCompletionStage.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.CompletionStage; 25 | import java.util.concurrent.ExecutionException; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.TypeSafeDiagnosingMatcher; 29 | 30 | /** 31 | * Creates a Matcher that matches a CompletionStage that has completed with a value that matches the 32 | * given Matcher. If the CompletionStage has not yet completed, this matcher waits for it to finish. 33 | */ 34 | class SuccessfullyCompletedBlockingCompletionStage 35 | extends TypeSafeDiagnosingMatcher> { 36 | 37 | private final Matcher matcher; 38 | 39 | SuccessfullyCompletedBlockingCompletionStage(final Matcher matcher) { 40 | this.matcher = Objects.requireNonNull(matcher); 41 | } 42 | 43 | @Override 44 | protected boolean matchesSafely( 45 | final CompletionStage future, final Description mismatchDescription) { 46 | try { 47 | final T item = future.toCompletableFuture().get(); 48 | if (matcher.matches(item)) { 49 | return true; 50 | } else { 51 | mismatchDescription.appendText("a stage that completed with a value that "); 52 | matcher.describeMismatch(item, mismatchDescription); 53 | return false; 54 | } 55 | } catch (InterruptedException e) { 56 | mismatchDescription.appendText("a stage that was interrupted"); 57 | return false; 58 | } catch (ExecutionException e) { 59 | mismatchDescription 60 | .appendText("a stage that completed exceptionally with ") 61 | .appendText(Utils.getStackTraceAsString(e.getCause())); 62 | return false; 63 | } 64 | } 65 | 66 | @Override 67 | public void describeTo(final Description description) { 68 | description 69 | .appendText("a stage that completed with a value that ") 70 | .appendDescriptionOf(matcher); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/SuccessfullyCompletedBlockingFuture.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.concurrent.Future; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.TypeSafeDiagnosingMatcher; 29 | 30 | class SuccessfullyCompletedBlockingFuture 31 | extends TypeSafeDiagnosingMatcher> { 32 | 33 | private final Matcher matcher; 34 | 35 | /** 36 | * Creates a new SuccessfullyCompletedBlockingFuture that matches a completed future where the 37 | * value matches the given matcher. 38 | */ 39 | SuccessfullyCompletedBlockingFuture(final Matcher matcher) { 40 | this.matcher = Objects.requireNonNull(matcher); 41 | } 42 | 43 | @Override 44 | protected boolean matchesSafely( 45 | final Future future, final Description mismatchDescription) { 46 | try { 47 | final T item = future.get(); 48 | if (matcher.matches(item)) { 49 | return true; 50 | } else { 51 | mismatchDescription.appendText("a future that completed with a value that "); 52 | matcher.describeMismatch(item, mismatchDescription); 53 | return false; 54 | } 55 | } catch (InterruptedException e) { 56 | mismatchDescription.appendText("a future that was interrupted"); 57 | return false; 58 | } catch (ExecutionException e) { 59 | mismatchDescription 60 | .appendText("a future that completed exceptionally with ") 61 | .appendText(Utils.getStackTraceAsString(e.getCause())); 62 | return false; 63 | } 64 | } 65 | 66 | @Override 67 | public void describeTo(final Description description) { 68 | description 69 | .appendText("a future that completed with a value that ") 70 | .appendDescriptionOf(matcher); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/SuccessfullyCompletedCompletionStage.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.Utils.getStackTraceAsString; 24 | 25 | import java.util.Objects; 26 | import java.util.concurrent.CompletableFuture; 27 | import java.util.concurrent.CompletionException; 28 | import java.util.concurrent.CompletionStage; 29 | import org.hamcrest.Description; 30 | import org.hamcrest.Matcher; 31 | import org.hamcrest.TypeSafeDiagnosingMatcher; 32 | 33 | /** 34 | * Creates a Matcher that matches a CompletionStage that has completed with a value that matches a 35 | * given Matcher. A CompletionStage that is not yet completed will not be matched. 36 | */ 37 | class SuccessfullyCompletedCompletionStage 38 | extends TypeSafeDiagnosingMatcher> { 39 | 40 | private final Matcher matcher; 41 | 42 | SuccessfullyCompletedCompletionStage(final Matcher matcher) { 43 | this.matcher = Objects.requireNonNull(matcher); 44 | } 45 | 46 | @Override 47 | protected boolean matchesSafely( 48 | final CompletionStage stage, final Description mismatchDescription) { 49 | final CompletableFuture future = stage.toCompletableFuture(); 50 | if (future.isDone()) { 51 | if (future.isCancelled()) { 52 | mismatchDescription.appendText("a stage that was cancelled"); 53 | return false; 54 | } else if (future.isCompletedExceptionally()) { 55 | try { 56 | future.getNow(null); 57 | throw new AssertionError( 58 | "This should never happen because the future has completed exceptionally."); 59 | } catch (CompletionException e) { 60 | mismatchDescription 61 | .appendText("a stage that completed exceptionally with ") 62 | .appendText(getStackTraceAsString(e.getCause())); 63 | } 64 | return false; 65 | } else { 66 | final T item = future.getNow(null); 67 | if (matcher.matches(item)) { 68 | return true; 69 | } else { 70 | mismatchDescription.appendText("a stage that completed to a value that "); 71 | matcher.describeMismatch(item, mismatchDescription); 72 | return false; 73 | } 74 | } 75 | } else { 76 | mismatchDescription.appendText("a stage that was not done"); 77 | return false; 78 | } 79 | } 80 | 81 | @Override 82 | public void describeTo(final Description description) { 83 | description.appendText("a stage that completed to a value that ").appendDescriptionOf(matcher); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/SuccessfullyCompletedFuture.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.util.Objects; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.concurrent.Future; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.TypeSafeDiagnosingMatcher; 29 | 30 | class SuccessfullyCompletedFuture extends TypeSafeDiagnosingMatcher> { 31 | 32 | private final Matcher matcher; 33 | 34 | /** 35 | * Creates a new SuccessfullyCompletedFuture that matches a completed future where the value 36 | * matches the given matcher. 37 | */ 38 | SuccessfullyCompletedFuture(final Matcher matcher) { 39 | this.matcher = Objects.requireNonNull(matcher); 40 | } 41 | 42 | @Override 43 | protected boolean matchesSafely( 44 | final Future future, final Description mismatchDescription) { 45 | if (future.isDone()) { 46 | if (future.isCancelled()) { 47 | mismatchDescription.appendText("a future that was cancelled"); 48 | return false; 49 | } else { 50 | try { 51 | final T item = future.get(); 52 | if (matcher.matches(item)) { 53 | return true; 54 | } else { 55 | mismatchDescription.appendText("a future that completed to a value that "); 56 | matcher.describeMismatch(item, mismatchDescription); 57 | return false; 58 | } 59 | } catch (InterruptedException e) { 60 | throw new AssertionError("This should never happen because the future is completed."); 61 | } catch (ExecutionException e) { 62 | mismatchDescription 63 | .appendText("a future that completed exceptionally with ") 64 | .appendText(Utils.getStackTraceAsString(e.getCause())); 65 | return false; 66 | } 67 | } 68 | } else { 69 | mismatchDescription.appendText("a future that was not completed"); 70 | return false; 71 | } 72 | } 73 | 74 | @Override 75 | public void describeTo(final Description description) { 76 | description.appendText("a future that completed to a value that ").appendDescriptionOf(matcher); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /future/src/main/java/com/spotify/hamcrest/future/Utils.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import java.io.PrintWriter; 24 | import java.io.StringWriter; 25 | 26 | class Utils { 27 | 28 | private Utils() {} 29 | 30 | /** 31 | * Returns a string containing the result of {@link Throwable#toString() toString()}, followed by 32 | * the full, recursive stack trace of {@code throwable}. Note that you probably should not be 33 | * parsing the resulting string; if you need programmatic access to the stack frames, you can call 34 | * {@link Throwable#getStackTrace()}. 35 | * 36 | *

We copy/pasted this from Guava because we don't want to depend on their whole library. This 37 | * class is duplicated in this repo's other packages because we want to keep this class 38 | * package-private. 39 | */ 40 | static String getStackTraceAsString(final Throwable throwable) { 41 | final StringWriter stringWriter = new StringWriter(); 42 | throwable.printStackTrace(new PrintWriter(stringWriter)); 43 | return stringWriter.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/CompletableFutureMatchersTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithException; 24 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithExceptionThat; 25 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithValue; 26 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithValueThat; 27 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageWillCompleteWithException; 28 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageWillCompleteWithExceptionThat; 29 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageWillCompleteWithValue; 30 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageWillCompleteWithValueThat; 31 | import static org.hamcrest.CoreMatchers.equalTo; 32 | import static org.hamcrest.CoreMatchers.is; 33 | import static org.hamcrest.CoreMatchers.isA; 34 | import static org.hamcrest.CoreMatchers.not; 35 | import static org.hamcrest.CoreMatchers.notNullValue; 36 | import static org.hamcrest.CoreMatchers.nullValue; 37 | import static org.hamcrest.CoreMatchers.sameInstance; 38 | import static org.junit.Assert.assertThat; 39 | 40 | import java.util.concurrent.CompletableFuture; 41 | import org.junit.Test; 42 | 43 | public class CompletableFutureMatchersTest { 44 | 45 | @Test 46 | public void exceptional() { 47 | final RuntimeException ex = new RuntimeException("oops"); 48 | 49 | final CompletableFuture cf = new CompletableFuture<>(); 50 | cf.completeExceptionally(ex); 51 | 52 | assertThat(cf, stageCompletedWithException()); 53 | assertThat(cf, stageCompletedWithExceptionThat(is(sameInstance(ex)))); 54 | assertThat(cf, stageCompletedWithExceptionThat(isA(RuntimeException.class))); 55 | assertThat(cf, stageWillCompleteWithException()); 56 | assertThat(cf, stageWillCompleteWithExceptionThat(is(sameInstance(ex)))); 57 | assertThat(cf, stageWillCompleteWithExceptionThat(isA(RuntimeException.class))); 58 | } 59 | 60 | @Test 61 | public void success() { 62 | final CompletableFuture cf = CompletableFuture.completedFuture("hi"); 63 | 64 | assertThat(cf, not(stageCompletedWithException())); 65 | assertThat(cf, not(stageCompletedWithExceptionThat(isA(Throwable.class)))); 66 | assertThat(cf, stageCompletedWithValue()); 67 | assertThat(cf, stageCompletedWithValueThat(not(nullValue()))); 68 | assertThat(cf, stageCompletedWithValueThat(notNullValue())); 69 | assertThat(cf, stageCompletedWithValueThat(equalTo("hi"))); 70 | 71 | assertThat(cf, not(stageWillCompleteWithException())); 72 | assertThat(cf, not(stageWillCompleteWithExceptionThat(isA(Throwable.class)))); 73 | assertThat(cf, stageWillCompleteWithValue()); 74 | assertThat(cf, stageWillCompleteWithValueThat(not(nullValue()))); 75 | assertThat(cf, stageWillCompleteWithValueThat(notNullValue())); 76 | assertThat(cf, stageWillCompleteWithValueThat(equalTo("hi"))); 77 | } 78 | 79 | @Test 80 | public void completedWithValueWhenExceptional() { 81 | final RuntimeException ex = new RuntimeException("oops"); 82 | 83 | final CompletableFuture cf = new CompletableFuture<>(); 84 | cf.completeExceptionally(ex); 85 | 86 | // ensure that the completedWithValue matcher correctly returns false from the matches() method 87 | // - this will fail if an exception is thrown instead 88 | assertThat(cf, not(stageCompletedWithValue())); 89 | assertThat(cf, not(stageWillCompleteWithValue())); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/ExceptionallyCompletedBlockingCompletionStageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.TestUtils.waitUntilInterrupted; 24 | import static java.util.concurrent.CompletableFuture.completedFuture; 25 | import static java.util.concurrent.CompletableFuture.runAsync; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.CoreMatchers.isA; 28 | import static org.junit.Assert.assertThat; 29 | 30 | import java.util.concurrent.CompletableFuture; 31 | import java.util.concurrent.CompletionStage; 32 | import org.hamcrest.Matcher; 33 | import org.hamcrest.StringDescription; 34 | import org.junit.Test; 35 | 36 | public class ExceptionallyCompletedBlockingCompletionStageTest { 37 | 38 | private static final Matcher> SUT = 39 | CompletableFutureMatchers.stageWillCompleteWithExceptionThat(isA(RuntimeException.class)); 40 | 41 | @Test 42 | public void testDescriptionFormatting() throws Exception { 43 | final StringDescription description = new StringDescription(); 44 | SUT.describeTo(description); 45 | 46 | assertThat( 47 | description.toString(), 48 | is( 49 | "a stage completing with an exception " 50 | + "that is an instance of java.lang.RuntimeException")); 51 | } 52 | 53 | @Test 54 | public void testMismatchFormatting() throws Exception { 55 | final StringDescription description = new StringDescription(); 56 | SUT.describeMismatch(completedFuture(2), description); 57 | 58 | assertThat(description.toString(), is("a stage that completed with a value that was <2>")); 59 | } 60 | 61 | @Test 62 | public void testCancelledMismatchFormatting() throws Exception { 63 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 64 | future.cancel(true); 65 | final StringDescription description = new StringDescription(); 66 | SUT.describeMismatch(future, description); 67 | 68 | assertThat(description.toString(), is("a stage that was cancelled")); 69 | } 70 | 71 | @Test 72 | public void testInterruptedMismatchFormatting() throws Exception { 73 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 74 | 75 | try { 76 | // Interrupt this current thread so that future.get() will throw InterruptedException 77 | Thread.currentThread().interrupt(); 78 | final StringDescription description = new StringDescription(); 79 | SUT.describeMismatch(future, description); 80 | 81 | assertThat(description.toString(), is("a stage that was interrupted")); 82 | } finally { 83 | // Clear the interrupted flag to avoid interference between tests 84 | Thread.interrupted(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/ExceptionallyCompletedBlockingFutureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.google.common.util.concurrent.Futures.immediateFailedFuture; 24 | import static com.google.common.util.concurrent.Futures.immediateFuture; 25 | import static com.spotify.hamcrest.future.FutureMatchers.futureWillCompleteWithExceptionThat; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.CoreMatchers.isA; 28 | import static org.hamcrest.Matchers.matchesPattern; 29 | import static org.junit.Assert.assertThat; 30 | 31 | import com.google.common.util.concurrent.SettableFuture; 32 | import java.util.concurrent.Future; 33 | import org.hamcrest.Matcher; 34 | import org.hamcrest.StringDescription; 35 | import org.junit.Test; 36 | 37 | public class ExceptionallyCompletedBlockingFutureTest { 38 | 39 | private static final Matcher> SUT = 40 | futureWillCompleteWithExceptionThat(isA(RuntimeException.class)); 41 | 42 | @Test 43 | public void testDescriptionFormatting() throws Exception { 44 | final StringDescription description = new StringDescription(); 45 | SUT.describeTo(description); 46 | 47 | assertThat( 48 | description.toString(), 49 | is( 50 | "a future that completed with an exception that is an " 51 | + "instance of java.lang.RuntimeException")); 52 | } 53 | 54 | @Test 55 | public void testMismatchFormatting() throws Exception { 56 | final StringDescription description = new StringDescription(); 57 | SUT.describeMismatch(immediateFuture(2), description); 58 | 59 | assertThat(description.toString(), is("a future that completed to a value that was <2>")); 60 | } 61 | 62 | @Test 63 | public void testExceptionalMismatchFormatting() throws Exception { 64 | final Throwable unexpectedException = new AssertionError("unexpected"); 65 | final StringDescription description = new StringDescription(); 66 | SUT.describeMismatch(immediateFailedFuture(unexpectedException), description); 67 | 68 | assertThat( 69 | description.toString(), 70 | matchesPattern("^a future completed exceptionally with .*AssertionError")); 71 | } 72 | 73 | @Test 74 | public void testInterruptedMismatchFormatting() throws Exception { 75 | final SettableFuture future = SettableFuture.create(); 76 | 77 | try { 78 | // Interrupt this current thread so that future.get() will throw InterruptedException 79 | Thread.currentThread().interrupt(); 80 | final StringDescription description = new StringDescription(); 81 | SUT.describeMismatch(future, description); 82 | 83 | assertThat(description.toString(), is("a future that was interrupted")); 84 | } finally { 85 | // Clear the interrupted flag to avoid interference between tests 86 | Thread.interrupted(); 87 | } 88 | } 89 | 90 | @Test 91 | public void testCancelledMismatchFormatting() throws Exception { 92 | final SettableFuture future = SettableFuture.create(); 93 | 94 | // Cancel the future 95 | future.cancel(true); 96 | final StringDescription description = new StringDescription(); 97 | SUT.describeMismatch(future, description); 98 | 99 | assertThat(description.toString(), is("a future that was cancelled")); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/ExceptionallyCompletedCompletionStageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithExceptionThat; 24 | import static com.spotify.hamcrest.future.TestUtils.waitUntilInterrupted; 25 | import static java.util.concurrent.CompletableFuture.completedFuture; 26 | import static java.util.concurrent.CompletableFuture.runAsync; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.CoreMatchers.isA; 29 | import static org.junit.Assert.assertThat; 30 | 31 | import java.util.concurrent.CompletableFuture; 32 | import java.util.concurrent.CompletionStage; 33 | import org.hamcrest.Matcher; 34 | import org.hamcrest.StringDescription; 35 | import org.junit.Test; 36 | 37 | public class ExceptionallyCompletedCompletionStageTest { 38 | 39 | private static final Matcher> SUT = 40 | stageCompletedWithExceptionThat(isA(RuntimeException.class)); 41 | 42 | @Test 43 | public void testDescriptionFormatting() throws Exception { 44 | final StringDescription description = new StringDescription(); 45 | SUT.describeTo(description); 46 | 47 | assertThat( 48 | description.toString(), 49 | is( 50 | "a stage that completed with an exception " 51 | + "that is an instance of java.lang.RuntimeException")); 52 | } 53 | 54 | @Test 55 | public void testMismatchFormatting() throws Exception { 56 | final StringDescription description = new StringDescription(); 57 | SUT.describeMismatch(completedFuture(2), description); 58 | 59 | assertThat(description.toString(), is("a stage that completed to a value that was <2>")); 60 | } 61 | 62 | @Test 63 | public void testCancelledMismatchFormatting() throws Exception { 64 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 65 | future.cancel(true); 66 | final StringDescription description = new StringDescription(); 67 | SUT.describeMismatch(future, description); 68 | 69 | assertThat(description.toString(), is("a stage that was cancelled")); 70 | } 71 | 72 | @Test 73 | public void testInterruptedMismatchFormatting() throws Exception { 74 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 75 | 76 | try { 77 | // Interrupt this current thread so that future.get() will throw InterruptedException 78 | Thread.currentThread().interrupt(); 79 | final StringDescription description = new StringDescription(); 80 | SUT.describeMismatch(future, description); 81 | 82 | assertThat(description.toString(), is("a stage that was not completed")); 83 | } finally { 84 | // Clear the interrupted flag to avoid interference between tests 85 | Thread.interrupted(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/ExceptionallyCompletedFutureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithExceptionThat; 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.hamcrest.CoreMatchers.isA; 26 | import static org.hamcrest.Matchers.matchesPattern; 27 | import static org.junit.Assert.assertThat; 28 | 29 | import com.google.common.util.concurrent.Futures; 30 | import com.google.common.util.concurrent.SettableFuture; 31 | import java.util.concurrent.Future; 32 | import org.hamcrest.Matcher; 33 | import org.hamcrest.StringDescription; 34 | import org.junit.Test; 35 | 36 | public class ExceptionallyCompletedFutureTest { 37 | 38 | private static final Matcher> SUT = 39 | futureCompletedWithExceptionThat(isA(RuntimeException.class)); 40 | 41 | @Test 42 | public void testDescriptionFormatting() throws Exception { 43 | final StringDescription description = new StringDescription(); 44 | SUT.describeTo(description); 45 | 46 | assertThat( 47 | description.toString(), 48 | is( 49 | "a future that completed with an exception that is an " 50 | + "instance of java.lang.RuntimeException")); 51 | } 52 | 53 | @Test 54 | public void testMismatchFormatting() throws Exception { 55 | final StringDescription description = new StringDescription(); 56 | SUT.describeMismatch(Futures.immediateFuture(2), description); 57 | 58 | assertThat(description.toString(), is("a future that completed to a value that was <2>")); 59 | } 60 | 61 | @Test 62 | public void testExceptionalMismatchFormatting() throws Exception { 63 | final Throwable unexpectedException = new AssertionError("unexpected"); 64 | final StringDescription description = new StringDescription(); 65 | SUT.describeMismatch(Futures.immediateFailedFuture(unexpectedException), description); 66 | 67 | assertThat( 68 | description.toString(), 69 | matchesPattern("^a future completed exceptionally with .*AssertionError")); 70 | } 71 | 72 | @Test 73 | public void testInterruptedMismatchFormatting() throws Exception { 74 | final SettableFuture future = SettableFuture.create(); 75 | 76 | try { 77 | // Interrupt this current thread so that future.get() will throw InterruptedException 78 | Thread.currentThread().interrupt(); 79 | final StringDescription description = new StringDescription(); 80 | SUT.describeMismatch(future, description); 81 | 82 | assertThat(description.toString(), is("a future that was not done")); 83 | } finally { 84 | // Clear the interrupted flag to avoid interference between tests 85 | Thread.interrupted(); 86 | } 87 | } 88 | 89 | @Test 90 | public void testCancelledMismatchFormatting() throws Exception { 91 | final SettableFuture future = SettableFuture.create(); 92 | 93 | try { 94 | // Cancel the future 95 | future.cancel(true); 96 | final StringDescription description = new StringDescription(); 97 | SUT.describeMismatch(future, description); 98 | 99 | assertThat(description.toString(), is("a future that was cancelled")); 100 | } finally { 101 | // This will cause the future's thread to throw InterruptedException and make it return 102 | future.cancel(true); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/FutureMatchersTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithException; 24 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithExceptionThat; 25 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithValue; 26 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithValueThat; 27 | import static com.spotify.hamcrest.future.FutureMatchers.futureWillCompleteWithException; 28 | import static com.spotify.hamcrest.future.FutureMatchers.futureWillCompleteWithExceptionThat; 29 | import static com.spotify.hamcrest.future.FutureMatchers.futureWillCompleteWithValue; 30 | import static com.spotify.hamcrest.future.FutureMatchers.futureWillCompleteWithValueThat; 31 | import static org.hamcrest.CoreMatchers.equalTo; 32 | import static org.hamcrest.CoreMatchers.is; 33 | import static org.hamcrest.CoreMatchers.isA; 34 | import static org.hamcrest.CoreMatchers.not; 35 | import static org.hamcrest.CoreMatchers.notNullValue; 36 | import static org.hamcrest.CoreMatchers.nullValue; 37 | import static org.hamcrest.CoreMatchers.sameInstance; 38 | import static org.junit.Assert.assertThat; 39 | 40 | import com.google.common.util.concurrent.Futures; 41 | import java.util.concurrent.Future; 42 | import org.junit.Test; 43 | 44 | public class FutureMatchersTest { 45 | 46 | @Test 47 | public void exceptional() { 48 | final RuntimeException ex = new RuntimeException("oops"); 49 | 50 | final Future cf = Futures.immediateFailedFuture(ex); 51 | 52 | assertThat(cf, futureCompletedWithException()); 53 | assertThat(cf, futureCompletedWithExceptionThat(is(sameInstance(ex)))); 54 | assertThat(cf, futureCompletedWithExceptionThat(isA(RuntimeException.class))); 55 | assertThat(cf, futureWillCompleteWithException()); 56 | assertThat(cf, futureWillCompleteWithExceptionThat(is(sameInstance(ex)))); 57 | assertThat(cf, futureWillCompleteWithExceptionThat(isA(RuntimeException.class))); 58 | } 59 | 60 | @Test 61 | public void success() { 62 | final Future cf = Futures.immediateFuture("hi"); 63 | 64 | assertThat(cf, not(futureCompletedWithException())); 65 | assertThat(cf, not(futureCompletedWithExceptionThat(isA(Throwable.class)))); 66 | assertThat(cf, futureCompletedWithValue()); 67 | assertThat(cf, futureCompletedWithValueThat(not(nullValue()))); 68 | assertThat(cf, futureCompletedWithValueThat(notNullValue())); 69 | assertThat(cf, futureCompletedWithValueThat(equalTo("hi"))); 70 | 71 | assertThat(cf, not(futureWillCompleteWithException())); 72 | assertThat(cf, not(futureWillCompleteWithExceptionThat(isA(Throwable.class)))); 73 | assertThat(cf, futureWillCompleteWithValue()); 74 | assertThat(cf, futureWillCompleteWithValueThat(not(nullValue()))); 75 | assertThat(cf, futureWillCompleteWithValueThat(notNullValue())); 76 | assertThat(cf, futureWillCompleteWithValueThat(equalTo("hi"))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/SuccessfullyCompletedBlockingCompletionStageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageWillCompleteWithValueThat; 24 | import static com.spotify.hamcrest.future.TestUtils.waitUntilInterrupted; 25 | import static java.util.concurrent.CompletableFuture.completedFuture; 26 | import static java.util.concurrent.CompletableFuture.runAsync; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.CoreMatchers.startsWith; 29 | import static org.junit.Assert.assertThat; 30 | 31 | import java.io.IOException; 32 | import java.util.concurrent.CompletableFuture; 33 | import java.util.concurrent.CompletionStage; 34 | import org.hamcrest.Matcher; 35 | import org.hamcrest.StringDescription; 36 | import org.junit.Test; 37 | 38 | public class SuccessfullyCompletedBlockingCompletionStageTest { 39 | 40 | private static final Matcher> SUT = 41 | stageWillCompleteWithValueThat(is(1)); 42 | 43 | @Test 44 | public void testDescriptionFormatting() throws Exception { 45 | final StringDescription description = new StringDescription(); 46 | SUT.describeTo(description); 47 | 48 | assertThat(description.toString(), is("a stage that completed with a value that is <1>")); 49 | } 50 | 51 | @Test 52 | public void testMismatchFormatting() throws Exception { 53 | final StringDescription description = new StringDescription(); 54 | SUT.describeMismatch(completedFuture(2), description); 55 | 56 | assertThat(description.toString(), is("a stage that completed with a value that was <2>")); 57 | } 58 | 59 | @Test 60 | public void testInterruptedMismatchFormatting() throws Exception { 61 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 62 | 63 | try { 64 | // Interrupt this current thread so that future.get() will throw InterruptedException 65 | Thread.currentThread().interrupt(); 66 | final StringDescription description = new StringDescription(); 67 | SUT.describeMismatch(future, description); 68 | 69 | assertThat(description.toString(), is("a stage that was interrupted")); 70 | } finally { 71 | // Clear the interrupted flag to avoid interference between tests 72 | Thread.interrupted(); 73 | } 74 | } 75 | 76 | @Test 77 | public void testFailedMismatchFormatting() throws Exception { 78 | final CompletableFuture future = new CompletableFuture<>(); 79 | // Make the future complete exceptionally with an exception that has a cause 80 | future.completeExceptionally(new IOException("error", new RuntimeException("cause"))); 81 | 82 | final StringDescription description = new StringDescription(); 83 | SUT.describeMismatch(future, description); 84 | 85 | assertThat( 86 | description.toString(), 87 | startsWith("a stage that completed exceptionally with java.io.IOException: error")); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/SuccessfullyCompletedBlockingFutureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.google.common.util.concurrent.Futures.immediateFailedFuture; 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.hamcrest.CoreMatchers.startsWith; 26 | import static org.junit.Assert.assertThat; 27 | 28 | import com.google.common.util.concurrent.Futures; 29 | import com.google.common.util.concurrent.ListenableFuture; 30 | import com.google.common.util.concurrent.SettableFuture; 31 | import java.io.IOException; 32 | import java.util.concurrent.Future; 33 | import org.hamcrest.Matcher; 34 | import org.hamcrest.StringDescription; 35 | import org.junit.Test; 36 | 37 | public class SuccessfullyCompletedBlockingFutureTest { 38 | 39 | private static final Matcher> SUT = 40 | FutureMatchers.futureWillCompleteWithValueThat(is(1)); 41 | 42 | @Test 43 | public void testDescriptionFormatting() throws Exception { 44 | final StringDescription description = new StringDescription(); 45 | SUT.describeTo(description); 46 | 47 | assertThat(description.toString(), is("a future that completed with a value that is <1>")); 48 | } 49 | 50 | @Test 51 | public void testMismatchFormatting() throws Exception { 52 | final StringDescription description = new StringDescription(); 53 | SUT.describeMismatch(Futures.immediateFuture(2), description); 54 | 55 | assertThat(description.toString(), is("a future that completed with a value that was <2>")); 56 | } 57 | 58 | @Test 59 | public void testInterruptedMismatchFormatting() throws Exception { 60 | final SettableFuture future = SettableFuture.create(); 61 | 62 | try { 63 | // Interrupt this current thread so that future.get() will throw InterruptedException 64 | Thread.currentThread().interrupt(); 65 | final StringDescription description = new StringDescription(); 66 | SUT.describeMismatch(future, description); 67 | 68 | assertThat(description.toString(), is("a future that was interrupted")); 69 | } finally { 70 | // Clear the interrupted flag to avoid interference between tests 71 | Thread.interrupted(); 72 | } 73 | } 74 | 75 | @Test 76 | public void testFailedMismatchFormatting() throws Exception { 77 | // Make the future complete exceptionally with an exception that has a cause 78 | final ListenableFuture future = 79 | immediateFailedFuture(new IOException("error", new RuntimeException("cause"))); 80 | 81 | final StringDescription description = new StringDescription(); 82 | SUT.describeMismatch(future, description); 83 | 84 | assertThat( 85 | description.toString(), 86 | startsWith("a future that completed exceptionally with java.io.IOException: error")); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/SuccessfullyCompletedCompletionStageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.spotify.hamcrest.future.CompletableFutureMatchers.stageCompletedWithValueThat; 24 | import static com.spotify.hamcrest.future.TestUtils.waitUntilInterrupted; 25 | import static java.util.concurrent.CompletableFuture.completedFuture; 26 | import static java.util.concurrent.CompletableFuture.runAsync; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.CoreMatchers.startsWith; 29 | import static org.junit.Assert.assertThat; 30 | 31 | import java.io.IOException; 32 | import java.util.concurrent.CompletableFuture; 33 | import java.util.concurrent.CompletionStage; 34 | import org.hamcrest.Matcher; 35 | import org.hamcrest.StringDescription; 36 | import org.junit.Test; 37 | 38 | public class SuccessfullyCompletedCompletionStageTest { 39 | 40 | private static final Matcher> SUT = 41 | stageCompletedWithValueThat(is(1)); 42 | 43 | @Test 44 | public void testDescriptionFormatting() throws Exception { 45 | final StringDescription description = new StringDescription(); 46 | SUT.describeTo(description); 47 | 48 | assertThat(description.toString(), is("a stage that completed to a value that is <1>")); 49 | } 50 | 51 | @Test 52 | public void testMismatchFormatting() throws Exception { 53 | final StringDescription description = new StringDescription(); 54 | SUT.describeMismatch(completedFuture(2), description); 55 | 56 | assertThat(description.toString(), is("a stage that completed to a value that was <2>")); 57 | } 58 | 59 | @Test 60 | public void testInterruptedMismatchFormatting() throws Exception { 61 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 62 | 63 | try { 64 | // Interrupt this current thread so that future.get() will throw InterruptedException 65 | Thread.currentThread().interrupt(); 66 | final StringDescription description = new StringDescription(); 67 | SUT.describeMismatch(future, description); 68 | 69 | assertThat(description.toString(), is("a stage that was not done")); 70 | } finally { 71 | // Clear the interrupted flag to avoid interference between tests 72 | Thread.interrupted(); 73 | } 74 | } 75 | 76 | @Test 77 | public void testCancelledMismatchFormatting() throws Exception { 78 | final CompletableFuture future = runAsync(waitUntilInterrupted()); 79 | 80 | try { 81 | // Cancel the future 82 | future.cancel(true); 83 | final StringDescription description = new StringDescription(); 84 | SUT.describeMismatch(future, description); 85 | 86 | assertThat(description.toString(), is("a stage that was cancelled")); 87 | } finally { 88 | // This will cause the future's thread to throw InterruptedException and make it return 89 | future.cancel(true); 90 | } 91 | } 92 | 93 | @Test 94 | public void testFailedMismatchFormatting() throws Exception { 95 | final CompletableFuture future = new CompletableFuture<>(); 96 | // Make the future complete exceptionally with an exception that has a cause 97 | future.completeExceptionally(new IOException("error", new RuntimeException("cause"))); 98 | 99 | final StringDescription description = new StringDescription(); 100 | SUT.describeMismatch(future, description); 101 | 102 | assertThat( 103 | description.toString(), 104 | startsWith("a stage that completed exceptionally with java.io.IOException: error")); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/SuccessfullyCompletedFutureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | import static com.google.common.util.concurrent.Futures.immediateFuture; 24 | import static com.spotify.hamcrest.future.FutureMatchers.futureCompletedWithValueThat; 25 | import static org.hamcrest.CoreMatchers.is; 26 | import static org.hamcrest.CoreMatchers.startsWith; 27 | import static org.junit.Assert.assertThat; 28 | 29 | import com.google.common.util.concurrent.Futures; 30 | import com.google.common.util.concurrent.ListenableFuture; 31 | import com.google.common.util.concurrent.SettableFuture; 32 | import java.io.IOException; 33 | import java.util.concurrent.Future; 34 | import org.hamcrest.Matcher; 35 | import org.hamcrest.StringDescription; 36 | import org.junit.Test; 37 | 38 | public class SuccessfullyCompletedFutureTest { 39 | 40 | private static final Matcher> SUT = futureCompletedWithValueThat(is(1)); 41 | 42 | @Test 43 | public void testDescriptionFormatting() throws Exception { 44 | final StringDescription description = new StringDescription(); 45 | SUT.describeTo(description); 46 | 47 | assertThat(description.toString(), is("a future that completed to a value that is <1>")); 48 | } 49 | 50 | @Test 51 | public void testMismatchFormatting() throws Exception { 52 | final StringDescription description = new StringDescription(); 53 | SUT.describeMismatch(immediateFuture(2), description); 54 | 55 | assertThat(description.toString(), is("a future that completed to a value that was <2>")); 56 | } 57 | 58 | @Test 59 | public void testInterruptedMismatchFormatting() throws Exception { 60 | final SettableFuture future = SettableFuture.create(); 61 | 62 | try { 63 | // Interrupt this current thread so that future.get() will throw InterruptedException 64 | Thread.currentThread().interrupt(); 65 | final StringDescription description = new StringDescription(); 66 | SUT.describeMismatch(future, description); 67 | 68 | assertThat(description.toString(), is("a future that was not completed")); 69 | } finally { 70 | // Clear the interrupted flag to avoid interference between tests 71 | Thread.interrupted(); 72 | } 73 | } 74 | 75 | @Test 76 | public void testCancelledMismatchFormatting() throws Exception { 77 | final SettableFuture future = SettableFuture.create(); 78 | 79 | try { 80 | // Cancel the future 81 | future.cancel(true); 82 | final StringDescription description = new StringDescription(); 83 | SUT.describeMismatch(future, description); 84 | 85 | assertThat(description.toString(), is("a future that was cancelled")); 86 | } finally { 87 | // Clear the interrupted flag to avoid interference between tests 88 | Thread.interrupted(); 89 | } 90 | } 91 | 92 | @Test 93 | public void testFailedMismatchFormatting() throws Exception { 94 | // Make the future complete exceptionally with an exception that has a cause 95 | final ListenableFuture future = 96 | Futures.immediateFailedFuture(new IOException("error", new RuntimeException("cause"))); 97 | 98 | final StringDescription description = new StringDescription(); 99 | SUT.describeMismatch(future, description); 100 | 101 | assertThat( 102 | description.toString(), 103 | startsWith("a future that completed exceptionally with java.io.IOException: error")); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /future/src/test/java/com/spotify/hamcrest/future/TestUtils.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-future 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.future; 22 | 23 | class TestUtils { 24 | 25 | /** 26 | * Run a new thread that waits until it's interrupted. 27 | * 28 | * @return Runnable 29 | */ 30 | static Runnable waitUntilInterrupted() { 31 | return new Runnable() { 32 | @Override 33 | public void run() { 34 | synchronized (this) { 35 | //noinspection InfiniteLoopStatement 36 | while (true) { 37 | try { 38 | this.wait(); 39 | } catch (InterruptedException e) { 40 | return; 41 | } 42 | } 43 | } 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /jackson/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hamcrest 5 | com.spotify 6 | 1.3.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hamcrest-jackson 11 | 12 | 13 | 14 | com.google.guava 15 | guava 16 | 17 | 18 | com.spotify 19 | hamcrest-util 20 | 21 | 22 | org.hamcrest 23 | hamcrest 24 | 25 | 26 | com.fasterxml.jackson.core 27 | jackson-core 28 | 29 | 30 | com.fasterxml.jackson.core 31 | jackson-databind 32 | 33 | 34 | junit 35 | junit 36 | test 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/AbstractJsonNodeMatcher.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import com.fasterxml.jackson.databind.node.JsonNodeType; 25 | import com.spotify.hamcrest.util.LanguageUtils; 26 | import java.util.Objects; 27 | import org.hamcrest.Description; 28 | import org.hamcrest.TypeSafeDiagnosingMatcher; 29 | 30 | public abstract class AbstractJsonNodeMatcher 31 | extends TypeSafeDiagnosingMatcher { 32 | 33 | private final JsonNodeType type; 34 | 35 | AbstractJsonNodeMatcher(final JsonNodeType type) { 36 | super(JsonNode.class); 37 | this.type = Objects.requireNonNull(type); 38 | } 39 | 40 | @Override 41 | protected boolean matchesSafely(JsonNode item, Description mismatchDescription) { 42 | if (item.getNodeType() == type) { 43 | @SuppressWarnings("unchecked") 44 | final A node = (A) item; 45 | 46 | return matchesNode(node, mismatchDescription); 47 | } else { 48 | mismatchDescription 49 | .appendText("was not ") 50 | .appendText(LanguageUtils.addArticle(type.name().toLowerCase())) 51 | .appendText(" node, but ") 52 | .appendText(LanguageUtils.addArticle(item.getNodeType().name().toLowerCase())) 53 | .appendText(" node"); 54 | return false; 55 | } 56 | } 57 | 58 | protected abstract boolean matchesNode(A node, Description mismatchDescription); 59 | } 60 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonArray.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static org.hamcrest.Matchers.is; 24 | import static org.hamcrest.core.IsAnything.anything; 25 | 26 | import com.fasterxml.jackson.databind.JsonNode; 27 | import com.fasterxml.jackson.databind.node.ArrayNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeType; 29 | import com.google.common.collect.ImmutableList; 30 | import java.util.Collection; 31 | import java.util.Objects; 32 | import org.hamcrest.Description; 33 | import org.hamcrest.Matcher; 34 | 35 | public class IsJsonArray extends AbstractJsonNodeMatcher { 36 | 37 | private final Matcher> elementsMatcher; 38 | 39 | private IsJsonArray(Matcher> elementsMatcher) { 40 | super(JsonNodeType.ARRAY); 41 | this.elementsMatcher = Objects.requireNonNull(elementsMatcher); 42 | } 43 | 44 | public static Matcher jsonArray() { 45 | return new IsJsonArray(is(anything())); 46 | } 47 | 48 | public static Matcher jsonArray( 49 | Matcher> elementsMatcher) { 50 | return new IsJsonArray(elementsMatcher); 51 | } 52 | 53 | public static Matcher jsonArray(final ArrayNode value) { 54 | return jsonArray(is(ImmutableList.copyOf(value))); 55 | } 56 | 57 | @Override 58 | protected boolean matchesNode(ArrayNode node, Description mismatchDescription) { 59 | final ImmutableList elements = ImmutableList.copyOf(node); 60 | if (elementsMatcher.matches(elements)) { 61 | return true; 62 | } else { 63 | mismatchDescription.appendText("was an array node whose elements "); 64 | elementsMatcher.describeMismatch(elements, mismatchDescription); 65 | return false; 66 | } 67 | } 68 | 69 | @Override 70 | public void describeTo(Description description) { 71 | description.appendText("an array node whose elements ").appendDescriptionOf(elementsMatcher); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonBoolean.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static org.hamcrest.core.Is.is; 24 | import static org.hamcrest.core.IsAnything.anything; 25 | 26 | import com.fasterxml.jackson.databind.JsonNode; 27 | import com.fasterxml.jackson.databind.node.BooleanNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeType; 29 | import java.util.Objects; 30 | import org.hamcrest.Description; 31 | import org.hamcrest.Matcher; 32 | 33 | public class IsJsonBoolean extends AbstractJsonNodeMatcher { 34 | 35 | private final Matcher booleanMatcher; 36 | 37 | private IsJsonBoolean(Matcher booleanMatcher) { 38 | super(JsonNodeType.BOOLEAN); 39 | this.booleanMatcher = Objects.requireNonNull(booleanMatcher); 40 | } 41 | 42 | public static Matcher jsonBoolean() { 43 | return new IsJsonBoolean(is(anything())); 44 | } 45 | 46 | public static Matcher jsonBoolean(boolean bool) { 47 | return new IsJsonBoolean(is(bool)); 48 | } 49 | 50 | public static Matcher jsonBoolean(Matcher booleanMatcher) { 51 | return new IsJsonBoolean(booleanMatcher); 52 | } 53 | 54 | public static Matcher jsonBoolean(final BooleanNode value) { 55 | return jsonBoolean(value.booleanValue()); 56 | } 57 | 58 | @Override 59 | protected boolean matchesNode(BooleanNode node, Description mismatchDescription) { 60 | final boolean value = node.asBoolean(); 61 | 62 | if (booleanMatcher.matches(value)) { 63 | return true; 64 | } else { 65 | mismatchDescription.appendText("was a boolean node with value that "); 66 | booleanMatcher.describeMismatch(value, mismatchDescription); 67 | return false; 68 | } 69 | } 70 | 71 | @Override 72 | public void describeTo(Description description) { 73 | description.appendText("a boolean node with value that ").appendDescriptionOf(booleanMatcher); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonMissing.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import com.fasterxml.jackson.databind.node.JsonNodeType; 25 | import com.fasterxml.jackson.databind.node.MissingNode; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | 29 | public class IsJsonMissing extends AbstractJsonNodeMatcher { 30 | private static final IsJsonMissing INSTANCE = new IsJsonMissing(); 31 | 32 | protected IsJsonMissing() { 33 | super(JsonNodeType.MISSING); 34 | } 35 | 36 | public static Matcher jsonMissing() { 37 | return INSTANCE; 38 | } 39 | 40 | @SuppressWarnings("unused") 41 | public static Matcher jsonMissing(final MissingNode value) { 42 | return jsonMissing(); 43 | } 44 | 45 | @Override 46 | protected boolean matchesNode(MissingNode node, Description mismatchDescription) { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void describeTo(Description description) { 52 | description.appendText("a missing node"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonNull.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import com.fasterxml.jackson.databind.node.JsonNodeType; 25 | import com.fasterxml.jackson.databind.node.NullNode; 26 | import org.hamcrest.Description; 27 | import org.hamcrest.Matcher; 28 | 29 | public class IsJsonNull extends AbstractJsonNodeMatcher { 30 | private static final IsJsonNull INSTANCE = new IsJsonNull(); 31 | 32 | private IsJsonNull() { 33 | super(JsonNodeType.NULL); 34 | } 35 | 36 | public static Matcher jsonNull() { 37 | return INSTANCE; 38 | } 39 | 40 | @SuppressWarnings("unused") 41 | public static Matcher jsonNull(final NullNode value) { 42 | return jsonNull(); 43 | } 44 | 45 | @Override 46 | protected boolean matchesNode(NullNode node, Description mismatchDescription) { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void describeTo(Description description) { 52 | description.appendText("a null node"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonNumber.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static org.hamcrest.core.Is.is; 24 | import static org.hamcrest.core.IsAnything.anything; 25 | 26 | import com.fasterxml.jackson.core.JsonParser; 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeType; 29 | import com.fasterxml.jackson.databind.node.NumericNode; 30 | import java.math.BigDecimal; 31 | import java.math.BigInteger; 32 | import java.util.Objects; 33 | import java.util.function.Function; 34 | import org.hamcrest.Description; 35 | import org.hamcrest.Matcher; 36 | 37 | /** 38 | * Matches JSON Number. 39 | * 40 | * @see #jsonNumber() 41 | * @see #jsonNumber(NumericNode) 42 | */ 43 | public class IsJsonNumber extends AbstractJsonNodeMatcher { 44 | 45 | private final Matcher numberMatcher; 46 | private final Function projection; 47 | 48 | private IsJsonNumber( 49 | final Matcher numberMatcher, final Function projection) { 50 | super(JsonNodeType.NUMBER); 51 | this.numberMatcher = Objects.requireNonNull(numberMatcher); 52 | this.projection = Objects.requireNonNull(projection); 53 | } 54 | 55 | /** 56 | * Matches a JSON Number. 57 | * 58 | * @return the json number matcher. 59 | */ 60 | public static Matcher jsonNumber() { 61 | // Function.identity() doesn't work since types change 62 | return new IsJsonNumber(is(anything()), n -> n); 63 | } 64 | 65 | /** 66 | * Matches a JSON Number. 67 | * 68 | * @param value the JSON Number value to be matched. 69 | * @return the json number matcher. 70 | */ 71 | public static Matcher jsonNumber(final NumericNode value) { 72 | final JsonParser.NumberType numberType = value.numberType(); 73 | switch (numberType) { 74 | case INT: 75 | return jsonInt(value.asInt()); 76 | case LONG: 77 | return jsonLong(value.asLong()); 78 | case BIG_INTEGER: 79 | return jsonBigInteger(value.bigIntegerValue()); 80 | case FLOAT: 81 | return jsonFloat(value.floatValue()); 82 | case DOUBLE: 83 | return jsonDouble(value.doubleValue()); 84 | case BIG_DECIMAL: 85 | return jsonBigDecimal(value.decimalValue()); 86 | default: 87 | throw new UnsupportedOperationException("Unsupported number type " + numberType); 88 | } 89 | } 90 | 91 | /** 92 | * Matches a JSON Int. 93 | * 94 | * @param number the int to be matched. 95 | * @return the json number matcher. 96 | */ 97 | public static Matcher jsonInt(int number) { 98 | return new IsJsonNumber(is(number), NumericNode::asInt); 99 | } 100 | 101 | /** 102 | * Matches a JSON Int. 103 | * 104 | * @param numberMatcher matcher for an integer value from a json value. 105 | * @return the json number matcher. 106 | */ 107 | public static Matcher jsonInt(Matcher numberMatcher) { 108 | return new IsJsonNumber(numberMatcher, NumericNode::asInt); 109 | } 110 | 111 | /** 112 | * Matches a JSON Long. 113 | * 114 | * @param number the long to be matched. 115 | * @return the json number matcher. 116 | */ 117 | public static Matcher jsonLong(long number) { 118 | return new IsJsonNumber(is(number), NumericNode::asLong); 119 | } 120 | 121 | /** 122 | * Matches a JSON Long. 123 | * 124 | * @param numberMatcher matcher for an long value from a json value. 125 | * @return the json number matcher. 126 | */ 127 | public static Matcher jsonLong(Matcher numberMatcher) { 128 | return new IsJsonNumber(numberMatcher, NumericNode::asLong); 129 | } 130 | 131 | /** 132 | * Matches a JSON Big Integer. 133 | * 134 | * @param number the big integer to be matched. 135 | * @return the json number matcher. 136 | */ 137 | public static Matcher jsonBigInteger(BigInteger number) { 138 | return new IsJsonNumber(is(number), NumericNode::bigIntegerValue); 139 | } 140 | 141 | /** 142 | * Matches a JSON Long. 143 | * 144 | * @param numberMatcher matcher for a big integer value from a json value. 145 | * @return the json number matcher. 146 | */ 147 | public static Matcher jsonBigInteger(Matcher numberMatcher) { 148 | return new IsJsonNumber(numberMatcher, NumericNode::bigIntegerValue); 149 | } 150 | 151 | /** 152 | * Matches a JSON Float. 153 | * 154 | * @param number the float to be matched. 155 | * @return the json number matcher. 156 | */ 157 | public static Matcher jsonFloat(float number) { 158 | return new IsJsonNumber(is(number), NumericNode::floatValue); 159 | } 160 | 161 | /** 162 | * Matches a JSON Float. 163 | * 164 | * @param numberMatcher matcher for a float value from a json value. 165 | * @return the json number matcher. 166 | */ 167 | public static Matcher jsonFloat(Matcher numberMatcher) { 168 | return new IsJsonNumber(numberMatcher, NumericNode::floatValue); 169 | } 170 | 171 | /** 172 | * Matches a JSON Double. 173 | * 174 | * @param number the double to be matched. 175 | * @return the json number matcher. 176 | */ 177 | public static Matcher jsonDouble(double number) { 178 | return new IsJsonNumber(is(number), NumericNode::asDouble); 179 | } 180 | 181 | /** 182 | * Matches a JSON Double. 183 | * 184 | * @param numberMatcher matcher for a double value from a json value. 185 | * @return the json number matcher. 186 | */ 187 | public static Matcher jsonDouble(Matcher numberMatcher) { 188 | return new IsJsonNumber(numberMatcher, NumericNode::asDouble); 189 | } 190 | 191 | /** 192 | * Matches a JSON Big Decimal. 193 | * 194 | * @param number the big decimal to be matched. 195 | * @return the json number matcher. 196 | */ 197 | public static Matcher jsonBigDecimal(BigDecimal number) { 198 | return new IsJsonNumber(is(number), NumericNode::decimalValue); 199 | } 200 | 201 | /** 202 | * Matches a Big Decimal Long. 203 | * 204 | * @param numberMatcher matcher for a big decimal value from a json value. 205 | * @return the json number matcher. 206 | */ 207 | public static Matcher jsonBigDecimal(Matcher numberMatcher) { 208 | return new IsJsonNumber(numberMatcher, NumericNode::decimalValue); 209 | } 210 | 211 | @Override 212 | protected boolean matchesNode(NumericNode node, Description mismatchDescription) { 213 | final Object number = projection.apply(node); 214 | 215 | if (numberMatcher.matches(number)) { 216 | return true; 217 | } else { 218 | mismatchDescription.appendText("was a number node with value that "); 219 | numberMatcher.describeMismatch(number, mismatchDescription); 220 | return false; 221 | } 222 | } 223 | 224 | @Override 225 | public void describeTo(Description description) { 226 | description.appendText("a number node with value that ").appendDescriptionOf(numberMatcher); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonObject.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import com.fasterxml.jackson.databind.node.ArrayNode; 25 | import com.fasterxml.jackson.databind.node.BooleanNode; 26 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 27 | import com.fasterxml.jackson.databind.node.JsonNodeType; 28 | import com.fasterxml.jackson.databind.node.MissingNode; 29 | import com.fasterxml.jackson.databind.node.NullNode; 30 | import com.fasterxml.jackson.databind.node.NumericNode; 31 | import com.fasterxml.jackson.databind.node.ObjectNode; 32 | import com.fasterxml.jackson.databind.node.TextNode; 33 | import com.spotify.hamcrest.util.DescriptionUtils; 34 | import java.util.Iterator; 35 | import java.util.LinkedHashMap; 36 | import java.util.Map; 37 | import java.util.Objects; 38 | import java.util.function.Consumer; 39 | import org.hamcrest.Description; 40 | import org.hamcrest.Matcher; 41 | import org.hamcrest.StringDescription; 42 | 43 | /** 44 | * Matches JSON Object. 45 | * 46 | * @see #jsonObject() 47 | * @see #jsonObject(ObjectNode) 48 | */ 49 | public class IsJsonObject extends AbstractJsonNodeMatcher { 50 | 51 | private final LinkedHashMap> entryMatchers; 52 | 53 | private IsJsonObject(final LinkedHashMap> entryMatchers) { 54 | super(JsonNodeType.OBJECT); 55 | this.entryMatchers = Objects.requireNonNull(entryMatchers); 56 | } 57 | 58 | /** 59 | * Creates a json matcher. 60 | * 61 | * @return instance of {@link IsJsonObject}. 62 | */ 63 | public static IsJsonObject jsonObject() { 64 | return new IsJsonObject(new LinkedHashMap<>()); 65 | } 66 | 67 | /** 68 | * Creates a json matcher. 69 | * 70 | * @param objectNode expected json {@link ObjectNode}. 71 | * @return instance of {@link IsJsonObject}. 72 | */ 73 | public static IsJsonObject jsonObject(final ObjectNode objectNode) { 74 | final Iterator> fields = objectNode.fields(); 75 | final LinkedHashMap> entryMatchers = new LinkedHashMap<>(); 76 | 77 | while (fields.hasNext()) { 78 | final Map.Entry field = fields.next(); 79 | entryMatchers.put(field.getKey(), createNodeMatcher(field.getValue())); 80 | } 81 | 82 | return new IsJsonObject(entryMatchers); 83 | } 84 | 85 | private static Matcher createNodeMatcher(final JsonNode value) { 86 | final JsonNodeType nodeType = value.getNodeType(); 87 | switch (nodeType) { 88 | case ARRAY: 89 | return IsJsonArray.jsonArray((ArrayNode) value); 90 | case BINARY: 91 | throw new UnsupportedOperationException( 92 | "Expected value contains a binary node, which is not implemented."); 93 | case BOOLEAN: 94 | return IsJsonBoolean.jsonBoolean((BooleanNode) value); 95 | case MISSING: 96 | return IsJsonMissing.jsonMissing((MissingNode) value); 97 | case NULL: 98 | return IsJsonNull.jsonNull((NullNode) value); 99 | case NUMBER: 100 | return IsJsonNumber.jsonNumber((NumericNode) value); 101 | case OBJECT: 102 | return IsJsonObject.jsonObject((ObjectNode) value); 103 | case POJO: 104 | throw new UnsupportedOperationException( 105 | "Expected value contains a POJO node, which is not implemented."); 106 | case STRING: 107 | return IsJsonText.jsonText((TextNode) value); 108 | default: 109 | throw new UnsupportedOperationException("Unsupported node type " + nodeType); 110 | } 111 | } 112 | 113 | /** 114 | * Expect that the value of a given key matches a value. 115 | * 116 | * @param key the key we want to match. 117 | * @param valueMatcher the matcher of the value. 118 | * @return a new instance of {@link IsJsonObject}. 119 | */ 120 | public IsJsonObject where(String key, Matcher valueMatcher) { 121 | final LinkedHashMap> newMap = 122 | new LinkedHashMap<>(entryMatchers); 123 | newMap.put(key, valueMatcher); 124 | return new IsJsonObject(newMap); 125 | } 126 | 127 | @Override 128 | protected boolean matchesNode(ObjectNode node, Description mismatchDescription) { 129 | LinkedHashMap> mismatchedKeys = new LinkedHashMap<>(); 130 | for (Map.Entry> entryMatcher : entryMatchers.entrySet()) { 131 | final String key = entryMatcher.getKey(); 132 | final Matcher valueMatcher = entryMatcher.getValue(); 133 | 134 | final JsonNode value = node.path(key); 135 | 136 | if (!valueMatcher.matches(value)) { 137 | mismatchedKeys.put(key, d -> valueMatcher.describeMismatch(value, d)); 138 | } 139 | } 140 | 141 | if (!mismatchedKeys.isEmpty()) { 142 | DescriptionUtils.describeNestedMismatches( 143 | entryMatchers.keySet(), mismatchDescription, mismatchedKeys, IsJsonObject::describeKey); 144 | return false; 145 | } 146 | return true; 147 | } 148 | 149 | @Override 150 | public void describeTo(Description description) { 151 | description.appendText("{\n"); 152 | for (Map.Entry> entryMatcher : entryMatchers.entrySet()) { 153 | final String key = entryMatcher.getKey(); 154 | final Matcher valueMatcher = entryMatcher.getValue(); 155 | 156 | description.appendText(" "); 157 | describeKey(key, description); 158 | description.appendText(": "); 159 | 160 | final Description innerDescription = new StringDescription(); 161 | valueMatcher.describeTo(innerDescription); 162 | DescriptionUtils.indentDescription(description, innerDescription); 163 | } 164 | description.appendText("}"); 165 | } 166 | 167 | private static void describeKey(final String key, final Description mismatchDescription) { 168 | mismatchDescription.appendText(jsonEscapeString(key)); 169 | } 170 | 171 | private static String jsonEscapeString(String string) { 172 | return JsonNodeFactory.instance.textNode(string).toString(); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonStringMatching.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static java.util.Objects.requireNonNull; 24 | import static org.hamcrest.Condition.matched; 25 | import static org.hamcrest.Condition.notMatched; 26 | 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.ObjectMapper; 29 | import java.io.IOException; 30 | import org.hamcrest.Condition; 31 | import org.hamcrest.Description; 32 | import org.hamcrest.Matcher; 33 | import org.hamcrest.TypeSafeDiagnosingMatcher; 34 | 35 | /** 36 | * Matcher for matching Json strings 37 | * 38 | *

This is useful as an entry point to matching larger structures without needing to use Jackson 39 | * explicitly. 40 | * 41 | *

42 |  *   
43 |  *     String myJson = "{\"key\": 1234}";
44 |  *     assertThat(myJson, isJsonStringMatching(jsonObject().where("key", jsonInt(1234)));
45 |  *   
46 |  * 
47 | */ 48 | public final class IsJsonStringMatching extends TypeSafeDiagnosingMatcher { 49 | 50 | private static final ObjectMapper MAPPER = new ObjectMapper(); 51 | 52 | public static Matcher isJsonStringMatching(final Matcher matcher) { 53 | return new IsJsonStringMatching(matcher); 54 | } 55 | 56 | private final Matcher matcher; 57 | 58 | private IsJsonStringMatching(final Matcher matcher) { 59 | this.matcher = requireNonNull(matcher, "matcher"); 60 | } 61 | 62 | @Override 63 | protected boolean matchesSafely(final String string, final Description description) { 64 | return parseJsonNode(string, description).matching(matcher); 65 | } 66 | 67 | private Condition parseJsonNode( 68 | final String string, final Description mismatchDescription) { 69 | if (string == null) { 70 | mismatchDescription.appendText(" but JSON string was null"); 71 | return notMatched(); 72 | } 73 | 74 | try { 75 | final JsonNode jsonNode = MAPPER.readTree(string); 76 | return matched(jsonNode, mismatchDescription); 77 | } catch (IOException e) { 78 | mismatchDescription 79 | .appendText(" but the string was not valid JSON ") 80 | .appendValue(e.getMessage()); 81 | return notMatched(); 82 | } 83 | } 84 | 85 | @Override 86 | public void describeTo(final Description description) { 87 | description.appendText("A JSON string that matches ").appendDescriptionOf(matcher); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/IsJsonText.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static org.hamcrest.core.Is.is; 24 | import static org.hamcrest.core.IsAnything.anything; 25 | 26 | import com.fasterxml.jackson.databind.JsonNode; 27 | import com.fasterxml.jackson.databind.node.JsonNodeType; 28 | import com.fasterxml.jackson.databind.node.TextNode; 29 | import java.util.Objects; 30 | import org.hamcrest.Description; 31 | import org.hamcrest.Matcher; 32 | 33 | public class IsJsonText extends AbstractJsonNodeMatcher { 34 | 35 | private final Matcher textMatcher; 36 | 37 | private IsJsonText(final Matcher textMatcher) { 38 | super(JsonNodeType.STRING); 39 | this.textMatcher = Objects.requireNonNull(textMatcher); 40 | } 41 | 42 | public static Matcher jsonText() { 43 | return new IsJsonText(is(anything())); 44 | } 45 | 46 | public static Matcher jsonText(String text) { 47 | return new IsJsonText(is(text)); 48 | } 49 | 50 | public static Matcher jsonText(Matcher textMatcher) { 51 | return new IsJsonText(textMatcher); 52 | } 53 | 54 | public static Matcher jsonText(final TextNode value) { 55 | return jsonText(value.asText()); 56 | } 57 | 58 | @Override 59 | protected boolean matchesNode(TextNode node, Description mismatchDescription) { 60 | final String value = node.asText(); 61 | if (textMatcher.matches(value)) { 62 | return true; 63 | } else { 64 | mismatchDescription.appendText("was a text node with value that "); 65 | textMatcher.describeMismatch(value, mismatchDescription); 66 | return false; 67 | } 68 | } 69 | 70 | @Override 71 | public void describeTo(Description description) { 72 | description.appendText("a text node with value that ").appendDescriptionOf(textMatcher); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /jackson/src/main/java/com/spotify/hamcrest/jackson/JsonMatchers.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import com.fasterxml.jackson.databind.JsonNode; 24 | import com.fasterxml.jackson.databind.node.ArrayNode; 25 | import com.fasterxml.jackson.databind.node.BooleanNode; 26 | import com.fasterxml.jackson.databind.node.MissingNode; 27 | import com.fasterxml.jackson.databind.node.NullNode; 28 | import com.fasterxml.jackson.databind.node.NumericNode; 29 | import com.fasterxml.jackson.databind.node.ObjectNode; 30 | import com.fasterxml.jackson.databind.node.TextNode; 31 | import java.math.BigDecimal; 32 | import java.math.BigInteger; 33 | import java.util.Collection; 34 | import org.hamcrest.Matcher; 35 | 36 | /** 37 | * Methods to instantiate different JSON Matchers. 38 | * 39 | *

These are helper methods that can be called directly from their implementations as well. 40 | */ 41 | public final class JsonMatchers { 42 | 43 | private JsonMatchers() {} 44 | 45 | public static Matcher jsonArray() { 46 | return IsJsonArray.jsonArray(); 47 | } 48 | 49 | public static Matcher jsonArray( 50 | Matcher> elementsMatcher) { 51 | return IsJsonArray.jsonArray(elementsMatcher); 52 | } 53 | 54 | public static Matcher jsonArray(ArrayNode value) { 55 | return IsJsonArray.jsonArray(value); 56 | } 57 | 58 | public static Matcher jsonBoolean() { 59 | return IsJsonBoolean.jsonBoolean(); 60 | } 61 | 62 | public static Matcher jsonBoolean(boolean bool) { 63 | return IsJsonBoolean.jsonBoolean(bool); 64 | } 65 | 66 | public static Matcher jsonBoolean(Matcher booleanMatcher) { 67 | return IsJsonBoolean.jsonBoolean(booleanMatcher); 68 | } 69 | 70 | public static Matcher jsonBoolean(BooleanNode value) { 71 | return IsJsonBoolean.jsonBoolean(value); 72 | } 73 | 74 | public static Matcher jsonMissing() { 75 | return IsJsonMissing.jsonMissing(); 76 | } 77 | 78 | public static Matcher jsonMissing(MissingNode value) { 79 | return IsJsonMissing.jsonMissing(value); 80 | } 81 | 82 | public static Matcher jsonNull() { 83 | return IsJsonNull.jsonNull(); 84 | } 85 | 86 | public static Matcher jsonNull(NullNode value) { 87 | return IsJsonNull.jsonNull(value); 88 | } 89 | 90 | public static Matcher jsonNumber() { 91 | return IsJsonNumber.jsonNumber(); 92 | } 93 | 94 | public static Matcher jsonNumber(NumericNode value) { 95 | return IsJsonNumber.jsonNumber(value); 96 | } 97 | 98 | public static Matcher jsonInt(int number) { 99 | return IsJsonNumber.jsonInt(number); 100 | } 101 | 102 | public static Matcher jsonInt(Matcher numberMatcher) { 103 | return IsJsonNumber.jsonInt(numberMatcher); 104 | } 105 | 106 | public static Matcher jsonLong(long number) { 107 | return IsJsonNumber.jsonLong(number); 108 | } 109 | 110 | public static Matcher jsonLong(Matcher numberMatcher) { 111 | return IsJsonNumber.jsonLong(numberMatcher); 112 | } 113 | 114 | public static Matcher jsonBigInteger(BigInteger number) { 115 | return IsJsonNumber.jsonBigInteger(number); 116 | } 117 | 118 | public static Matcher jsonBigInteger(Matcher numberMatcher) { 119 | return IsJsonNumber.jsonBigInteger(numberMatcher); 120 | } 121 | 122 | public static Matcher jsonFloat(float number) { 123 | return IsJsonNumber.jsonFloat(number); 124 | } 125 | 126 | public static Matcher jsonFloat(Matcher numberMatcher) { 127 | return IsJsonNumber.jsonFloat(numberMatcher); 128 | } 129 | 130 | public static Matcher jsonDouble(double number) { 131 | return IsJsonNumber.jsonDouble(number); 132 | } 133 | 134 | public static Matcher jsonDouble(Matcher numberMatcher) { 135 | return IsJsonNumber.jsonDouble(numberMatcher); 136 | } 137 | 138 | public static Matcher jsonBigDecimal(BigDecimal number) { 139 | return IsJsonNumber.jsonBigDecimal(number); 140 | } 141 | 142 | public static Matcher jsonBigDecimal(Matcher numberMatcher) { 143 | return IsJsonNumber.jsonBigDecimal(numberMatcher); 144 | } 145 | 146 | public static IsJsonObject jsonObject() { 147 | return IsJsonObject.jsonObject(); 148 | } 149 | 150 | public static IsJsonObject jsonObject(final ObjectNode objectNode) { 151 | return IsJsonObject.jsonObject(objectNode); 152 | } 153 | 154 | public static Matcher isJsonStringMatching(final Matcher matcher) { 155 | return IsJsonStringMatching.isJsonStringMatching(matcher); 156 | } 157 | 158 | public static Matcher jsonText() { 159 | return IsJsonText.jsonText(); 160 | } 161 | 162 | public static Matcher jsonText(String text) { 163 | return IsJsonText.jsonText(text); 164 | } 165 | 166 | public static Matcher jsonText(Matcher textMatcher) { 167 | return IsJsonText.jsonText(textMatcher); 168 | } 169 | 170 | public static Matcher jsonText(TextNode value) { 171 | return IsJsonText.jsonText(value); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonArrayTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonArray.jsonArray; 24 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonInt; 25 | import static com.spotify.hamcrest.jackson.IsJsonText.jsonText; 26 | import static org.hamcrest.Matchers.anything; 27 | import static org.hamcrest.Matchers.contains; 28 | import static org.hamcrest.Matchers.containsInAnyOrder; 29 | import static org.hamcrest.Matchers.empty; 30 | import static org.hamcrest.Matchers.emptyIterable; 31 | import static org.hamcrest.core.Is.is; 32 | import static org.junit.Assert.assertThat; 33 | 34 | import com.fasterxml.jackson.databind.JsonNode; 35 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 36 | import org.hamcrest.Matcher; 37 | import org.hamcrest.StringDescription; 38 | import org.junit.Test; 39 | 40 | public class IsJsonArrayTest { 41 | 42 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 43 | 44 | @Test 45 | public void testType() throws Exception { 46 | final Matcher sut = jsonArray(); 47 | 48 | assertThat(NF.arrayNode(), is(sut)); 49 | } 50 | 51 | @Test 52 | public void testEmptyIterable() throws Exception { 53 | final Matcher sut = jsonArray(emptyIterable()); 54 | 55 | assertThat(NF.arrayNode(), is(sut)); 56 | } 57 | 58 | @Test 59 | public void testEmpty() throws Exception { 60 | final Matcher sut = jsonArray(empty()); 61 | 62 | assertThat(NF.arrayNode(), is(sut)); 63 | } 64 | 65 | @Test 66 | public void testLiteral() throws Exception { 67 | final Matcher sut = jsonArray(NF.arrayNode().add(1).add(2)); 68 | 69 | assertThat(NF.arrayNode().add(1).add(2), is(sut)); 70 | } 71 | 72 | @SuppressWarnings("unchecked") 73 | @Test 74 | public void testContains() throws Exception { 75 | final Matcher sut = jsonArray(contains(jsonText("a"), jsonInt(1))); 76 | 77 | assertThat(NF.arrayNode().add("a").add(1), is(sut)); 78 | } 79 | 80 | @SuppressWarnings("unchecked") 81 | @Test 82 | public void testContainsInAnyOrder() throws Exception { 83 | final Matcher sut = jsonArray(containsInAnyOrder(jsonText("a"), jsonInt(1))); 84 | 85 | assertThat(NF.arrayNode().add(1).add("a"), is(sut)); 86 | } 87 | 88 | @Test 89 | public void testMismatchElements() throws Exception { 90 | final Matcher sut = jsonArray(contains(jsonText("a"))); 91 | 92 | final StringDescription description = new StringDescription(); 93 | sut.describeMismatch(NF.arrayNode().add(1), description); 94 | 95 | assertThat( 96 | description.toString(), 97 | is("was an array node whose elements item 0: was not a string node, but a number node")); 98 | } 99 | 100 | @Test 101 | public void testMismatchType() throws Exception { 102 | final Matcher sut = jsonArray(contains(jsonText("a"))); 103 | 104 | final StringDescription description = new StringDescription(); 105 | sut.describeMismatch(NF.booleanNode(false), description); 106 | 107 | assertThat(description.toString(), is("was not an array node, but a boolean node")); 108 | } 109 | 110 | @Test 111 | public void testDescription() throws Exception { 112 | final Matcher sut = jsonArray(is(anything())); 113 | 114 | final StringDescription description = new StringDescription(); 115 | sut.describeTo(description); 116 | 117 | assertThat(description.toString(), is("an array node whose elements is ANYTHING")); 118 | } 119 | 120 | @Test 121 | public void testDescriptionForEmptyConstructor() throws Exception { 122 | final Matcher sut = jsonArray(); 123 | 124 | final StringDescription description = new StringDescription(); 125 | sut.describeTo(description); 126 | 127 | assertThat(description.toString(), is("an array node whose elements is ANYTHING")); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonBooleanTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonBoolean.jsonBoolean; 24 | import static org.hamcrest.core.Is.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 29 | import org.hamcrest.Matcher; 30 | import org.hamcrest.StringDescription; 31 | import org.junit.Test; 32 | 33 | public class IsJsonBooleanTest { 34 | 35 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 36 | 37 | @Test 38 | public void testType() throws Exception { 39 | final Matcher sut = jsonBoolean(); 40 | 41 | assertThat(NF.booleanNode(false), is(sut)); 42 | } 43 | 44 | @Test 45 | public void testLiteral() throws Exception { 46 | final Matcher sut = jsonBoolean(NF.booleanNode(false)); 47 | 48 | assertThat(NF.booleanNode(false), is(sut)); 49 | } 50 | 51 | @Test 52 | public void testMatchValue() throws Exception { 53 | final Matcher sut = jsonBoolean(false); 54 | 55 | assertThat(NF.booleanNode(false), is(sut)); 56 | } 57 | 58 | @Test 59 | public void testMatchMatcher() throws Exception { 60 | final Matcher sut = jsonBoolean(is(false)); 61 | 62 | assertThat(NF.booleanNode(false), is(sut)); 63 | } 64 | 65 | @Test 66 | public void testMismatchValue() throws Exception { 67 | final Matcher sut = jsonBoolean(false); 68 | 69 | final StringDescription description = new StringDescription(); 70 | sut.describeMismatch(NF.booleanNode(true), description); 71 | 72 | assertThat(description.toString(), is("was a boolean node with value that was ")); 73 | } 74 | 75 | @Test 76 | public void testMismatchType() throws Exception { 77 | final Matcher sut = jsonBoolean(false); 78 | 79 | final StringDescription description = new StringDescription(); 80 | sut.describeMismatch(NF.textNode("goat"), description); 81 | 82 | assertThat(description.toString(), is("was not a boolean node, but a string node")); 83 | } 84 | 85 | @Test 86 | public void testDescription() throws Exception { 87 | final Matcher sut = jsonBoolean(false); 88 | 89 | final StringDescription description = new StringDescription(); 90 | sut.describeTo(description); 91 | 92 | assertThat(description.toString(), is("a boolean node with value that is ")); 93 | } 94 | 95 | @Test 96 | public void testDescriptionForEmptyConstructor() throws Exception { 97 | final Matcher sut = jsonBoolean(); 98 | 99 | final StringDescription description = new StringDescription(); 100 | sut.describeTo(description); 101 | 102 | assertThat(description.toString(), is("a boolean node with value that is ANYTHING")); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonMissingTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonMissing.jsonMissing; 24 | import static org.hamcrest.core.Is.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 29 | import com.fasterxml.jackson.databind.node.MissingNode; 30 | import org.hamcrest.Matcher; 31 | import org.hamcrest.StringDescription; 32 | import org.junit.Test; 33 | 34 | public class IsJsonMissingTest { 35 | 36 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 37 | 38 | @Test 39 | public void testType() throws Exception { 40 | final Matcher sut = jsonMissing(); 41 | 42 | assertThat(MissingNode.getInstance(), is(sut)); 43 | } 44 | 45 | @Test 46 | public void testLiteral() throws Exception { 47 | final Matcher sut = jsonMissing(MissingNode.getInstance()); 48 | 49 | assertThat(MissingNode.getInstance(), is(sut)); 50 | } 51 | 52 | @Test 53 | public void testMatch() throws Exception { 54 | final Matcher sut = jsonMissing(); 55 | 56 | assertThat(MissingNode.getInstance(), is(sut)); 57 | } 58 | 59 | @Test 60 | public void testMismatchType() throws Exception { 61 | final Matcher sut = jsonMissing(); 62 | 63 | final StringDescription description = new StringDescription(); 64 | sut.describeMismatch(NF.textNode("goat"), description); 65 | 66 | assertThat(description.toString(), is("was not a missing node, but a string node")); 67 | } 68 | 69 | @Test 70 | public void testDescription() throws Exception { 71 | final Matcher sut = jsonMissing(); 72 | 73 | final StringDescription description = new StringDescription(); 74 | sut.describeTo(description); 75 | 76 | assertThat(description.toString(), is("a missing node")); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonNullTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonNull.jsonNull; 24 | import static org.hamcrest.core.Is.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | import com.fasterxml.jackson.databind.JsonNode; 28 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 29 | import com.fasterxml.jackson.databind.node.NullNode; 30 | import org.hamcrest.Matcher; 31 | import org.hamcrest.StringDescription; 32 | import org.junit.Test; 33 | 34 | public class IsJsonNullTest { 35 | 36 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 37 | 38 | @Test 39 | public void testType() throws Exception { 40 | final Matcher sut = jsonNull(); 41 | 42 | assertThat(NullNode.getInstance(), is(sut)); 43 | } 44 | 45 | @Test 46 | public void testLiteral() throws Exception { 47 | final Matcher sut = jsonNull(NF.nullNode()); 48 | 49 | assertThat(NF.nullNode(), is(sut)); 50 | } 51 | 52 | @Test 53 | public void testMatch() throws Exception { 54 | final Matcher sut = jsonNull(); 55 | 56 | assertThat(NF.nullNode(), is(sut)); 57 | } 58 | 59 | @Test 60 | public void testMismatchType() throws Exception { 61 | final Matcher sut = jsonNull(); 62 | 63 | final StringDescription description = new StringDescription(); 64 | sut.describeMismatch(NF.textNode("goat"), description); 65 | 66 | assertThat(description.toString(), is("was not a null node, but a string node")); 67 | } 68 | 69 | @Test 70 | public void testDescription() throws Exception { 71 | final Matcher sut = jsonNull(); 72 | 73 | final StringDescription description = new StringDescription(); 74 | sut.describeTo(description); 75 | 76 | assertThat(description.toString(), is("a null node")); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonNumberTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonBigDecimal; 24 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonBigInteger; 25 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonDouble; 26 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonFloat; 27 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonInt; 28 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonLong; 29 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonNumber; 30 | import static org.hamcrest.core.Is.is; 31 | import static org.junit.Assert.assertThat; 32 | 33 | import com.fasterxml.jackson.databind.JsonNode; 34 | import com.fasterxml.jackson.databind.node.BigIntegerNode; 35 | import com.fasterxml.jackson.databind.node.DecimalNode; 36 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 37 | import java.math.BigDecimal; 38 | import java.math.BigInteger; 39 | import org.hamcrest.Matcher; 40 | import org.hamcrest.StringDescription; 41 | import org.junit.Test; 42 | 43 | public class IsJsonNumberTest { 44 | 45 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 46 | 47 | @Test 48 | public void testType() throws Exception { 49 | final Matcher sut = jsonNumber(); 50 | 51 | assertThat(NF.numberNode(1), is(sut)); 52 | } 53 | 54 | @Test 55 | public void testLiteralInt() throws Exception { 56 | final Matcher sut = jsonNumber(NF.numberNode(1)); 57 | 58 | assertThat(NF.numberNode(1), is(sut)); 59 | } 60 | 61 | @Test 62 | public void testLiteralLong() throws Exception { 63 | final Matcher sut = jsonNumber(NF.numberNode(1L)); 64 | 65 | assertThat(NF.numberNode(1L), is(sut)); 66 | } 67 | 68 | @Test 69 | public void testLiteralBigInteger() throws Exception { 70 | final Matcher sut = jsonNumber(BigIntegerNode.valueOf(BigInteger.ONE)); 71 | 72 | assertThat(NF.numberNode(BigInteger.ONE), is(sut)); 73 | } 74 | 75 | @Test 76 | public void testLiteralFloat() throws Exception { 77 | final Matcher sut = jsonNumber(NF.numberNode(1f)); 78 | 79 | assertThat(NF.numberNode(1f), is(sut)); 80 | } 81 | 82 | @Test 83 | public void testLiteralDouble() throws Exception { 84 | final Matcher sut = jsonNumber(NF.numberNode(1d)); 85 | 86 | assertThat(NF.numberNode(1d), is(sut)); 87 | } 88 | 89 | @Test 90 | public void testLiteralBigDecimal() throws Exception { 91 | final Matcher sut = jsonNumber(DecimalNode.valueOf(BigDecimal.ONE)); 92 | 93 | assertThat(NF.numberNode(BigDecimal.ONE), is(sut)); 94 | } 95 | 96 | @Test 97 | public void testMatchIntValue() throws Exception { 98 | final Matcher sut = jsonInt(1); 99 | 100 | assertThat(NF.numberNode(1), is(sut)); 101 | } 102 | 103 | @Test 104 | public void testMatchIntMatcher() throws Exception { 105 | final Matcher sut = jsonInt(is(1)); 106 | 107 | assertThat(NF.numberNode(1), is(sut)); 108 | } 109 | 110 | @Test 111 | public void testMatchLongValue() throws Exception { 112 | final Matcher sut = jsonLong(1L); 113 | 114 | assertThat(NF.numberNode(1), is(sut)); 115 | } 116 | 117 | @Test 118 | public void testMatchLongMatcher() throws Exception { 119 | final Matcher sut = jsonLong(is(1L)); 120 | 121 | assertThat(NF.numberNode(1), is(sut)); 122 | } 123 | 124 | @Test 125 | public void testMatchBigIntegerValue() throws Exception { 126 | final Matcher sut = jsonBigInteger(BigInteger.ONE); 127 | 128 | assertThat(NF.numberNode(BigInteger.ONE), is(sut)); 129 | } 130 | 131 | @Test 132 | public void testMatchBigIntegerMatcher() throws Exception { 133 | final Matcher sut = jsonBigInteger(is(BigInteger.ONE)); 134 | 135 | assertThat(NF.numberNode(BigInteger.ONE), is(sut)); 136 | } 137 | 138 | @Test 139 | public void testMatchFloatValue() throws Exception { 140 | final Matcher sut = jsonFloat(1f); 141 | 142 | assertThat(NF.numberNode(1f), is(sut)); 143 | } 144 | 145 | @Test 146 | public void testMatchFloatMatcher() throws Exception { 147 | final Matcher sut = jsonFloat(is(1f)); 148 | 149 | assertThat(NF.numberNode(1f), is(sut)); 150 | } 151 | 152 | @Test 153 | public void testMatchDoubleValue() throws Exception { 154 | final Matcher sut = jsonDouble(1d); 155 | 156 | assertThat(NF.numberNode(1d), is(sut)); 157 | } 158 | 159 | @Test 160 | public void testMatchDoubleMatcher() throws Exception { 161 | final Matcher sut = jsonDouble(is(1d)); 162 | 163 | assertThat(NF.numberNode(1d), is(sut)); 164 | } 165 | 166 | @Test 167 | public void testMatchBigDecimalValue() throws Exception { 168 | final Matcher sut = jsonBigDecimal(BigDecimal.ONE); 169 | 170 | assertThat(NF.numberNode(BigDecimal.ONE), is(sut)); 171 | } 172 | 173 | @Test 174 | public void testMatchBigDecimalMatcher() throws Exception { 175 | final Matcher sut = jsonBigDecimal(is(BigDecimal.ONE)); 176 | 177 | assertThat(NF.numberNode(BigDecimal.ONE), is(sut)); 178 | } 179 | 180 | @Test 181 | public void testMismatchValue() throws Exception { 182 | final Matcher sut = jsonInt(1); 183 | 184 | final StringDescription description = new StringDescription(); 185 | sut.describeMismatch(NF.numberNode(2), description); 186 | 187 | assertThat(description.toString(), is("was a number node with value that was <2>")); 188 | } 189 | 190 | @Test 191 | public void testMismatchType() throws Exception { 192 | final Matcher sut = jsonInt(1); 193 | 194 | final StringDescription description = new StringDescription(); 195 | sut.describeMismatch(NF.textNode("goat"), description); 196 | 197 | assertThat(description.toString(), is("was not a number node, but a string node")); 198 | } 199 | 200 | @Test 201 | public void testDescription() throws Exception { 202 | final Matcher sut = jsonInt(1); 203 | 204 | final StringDescription description = new StringDescription(); 205 | sut.describeTo(description); 206 | 207 | assertThat(description.toString(), is("a number node with value that is <1>")); 208 | } 209 | 210 | @Test 211 | public void testDescriptionForEmptyConstructor() throws Exception { 212 | final Matcher sut = jsonNumber(); 213 | 214 | final StringDescription description = new StringDescription(); 215 | sut.describeTo(description); 216 | 217 | assertThat(description.toString(), is("a number node with value that is ANYTHING")); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonObjectTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonBoolean.jsonBoolean; 24 | import static com.spotify.hamcrest.jackson.IsJsonNull.jsonNull; 25 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonInt; 26 | import static com.spotify.hamcrest.jackson.IsJsonObject.jsonObject; 27 | import static org.hamcrest.core.Is.is; 28 | import static org.junit.Assert.assertThat; 29 | 30 | import com.fasterxml.jackson.databind.JsonNode; 31 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 32 | import com.fasterxml.jackson.databind.node.ObjectNode; 33 | import org.hamcrest.Matcher; 34 | import org.hamcrest.StringDescription; 35 | import org.junit.Test; 36 | 37 | public class IsJsonObjectTest { 38 | 39 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 40 | 41 | @Test 42 | public void testType() throws Exception { 43 | final Matcher sut = jsonObject(); 44 | 45 | assertThat(NF.objectNode(), is(sut)); 46 | } 47 | 48 | @Test 49 | public void testLiteral() throws Exception { 50 | final Matcher sut = jsonObject(NF.objectNode().put("a", 1).put("b", false)); 51 | 52 | assertThat(NF.objectNode().put("a", 1).put("b", false), is(sut)); 53 | } 54 | 55 | @Test 56 | public void testField() throws Exception { 57 | final Matcher sut = jsonObject().where("foo", is(jsonInt(1))); 58 | 59 | assertThat(NF.objectNode().put("foo", 1), is(sut)); 60 | } 61 | 62 | @Test 63 | public void testFields() throws Exception { 64 | final Matcher sut = 65 | jsonObject().where("foo", is(jsonInt(1))).where("bar", is(jsonBoolean(false))); 66 | 67 | assertThat(NF.objectNode().put("foo", 1).put("bar", false), is(sut)); 68 | } 69 | 70 | @Test 71 | public void testNested() throws Exception { 72 | final Matcher sut = 73 | jsonObject() 74 | .where("foo", is(jsonInt(1))) 75 | .where("bar", is(jsonBoolean(false))) 76 | .where("baz", is(jsonObject().where("foo", is(jsonNull())))); 77 | 78 | assertThat( 79 | NF.objectNode() 80 | .put("foo", 1) 81 | .put("bar", false) 82 | .set("baz", NF.objectNode().set("foo", NF.nullNode())), 83 | is(sut)); 84 | } 85 | 86 | @Test 87 | public void lastNodeMismatchHasNoTrailingEllipsisButHasLeading() throws Exception { 88 | final Matcher sut = 89 | jsonObject().where("foo", is(jsonInt(1))).where("bar", is(jsonBoolean(false))); 90 | 91 | final StringDescription description = new StringDescription(); 92 | sut.describeMismatch(NF.objectNode().put("foo", 1).put("bar", true), description); 93 | 94 | assertThat( 95 | description.toString(), 96 | is("{\n" + " ...\n" + " \"bar\": was a boolean node with value that was \n" + "}")); 97 | } 98 | 99 | @Test 100 | public void firstNodeMismatchHasNoLeadingEllipsisButWithTrailing() throws Exception { 101 | final Matcher sut = 102 | jsonObject().where("foo", is(jsonInt(1))).where("bar", is(jsonBoolean(false))); 103 | 104 | final StringDescription description = new StringDescription(); 105 | sut.describeMismatch(NF.objectNode().put("foo", 2).put("bar", false), description); 106 | 107 | assertThat( 108 | description.toString(), 109 | is("{\n" + " \"foo\": was a number node with value that was <2>\n" + " ...\n" + "}")); 110 | } 111 | 112 | @Test 113 | public void allMismatchesWillHaveNoTrailingOrLeadingEllipsis() throws Exception { 114 | final Matcher sut = 115 | jsonObject().where("foo", is(jsonInt(1))).where("bar", is(jsonBoolean(false))); 116 | 117 | final StringDescription description = new StringDescription(); 118 | sut.describeMismatch(NF.objectNode().put("foo", 2).put("bar", true), description); 119 | 120 | assertThat( 121 | description.toString(), 122 | is( 123 | "{\n" 124 | + " \"foo\": was a number node with value that was <2>\n" 125 | + " \"bar\": was a boolean node with value that was \n" 126 | + "}")); 127 | } 128 | 129 | @Test 130 | public void testMismatchNested() throws Exception { 131 | final Matcher sut = 132 | is( 133 | jsonObject() 134 | .where("foo", is(jsonInt(1))) 135 | .where("bar", is(jsonBoolean(true))) 136 | .where("baz", is(jsonObject().where("foo", is(jsonNull()))))); 137 | 138 | final StringDescription description = new StringDescription(); 139 | sut.describeMismatch( 140 | NF.objectNode() 141 | .put("foo", 1) 142 | .put("bar", true) 143 | .set("baz", NF.objectNode().set("foo", NF.booleanNode(false))), 144 | description); 145 | 146 | assertThat( 147 | description.toString(), 148 | is( 149 | "{\n" 150 | + " ...\n" 151 | + " \"baz\": {\n" 152 | + " \"foo\": was not a null node, but a boolean node\n" 153 | + " }\n" 154 | + "}")); 155 | } 156 | 157 | @Test 158 | public void testMismatchType() throws Exception { 159 | final Matcher sut = jsonObject().where("foo", is(jsonNull())); 160 | 161 | final StringDescription description = new StringDescription(); 162 | sut.describeMismatch(NF.booleanNode(false), description); 163 | 164 | assertThat(description.toString(), is("was not an object node, but a boolean node")); 165 | } 166 | 167 | @Test 168 | public void testMultipleMismatchesReportsAllMismatches() throws Exception { 169 | final Matcher sut = 170 | jsonObject() 171 | .where("foo", is(jsonInt(1))) 172 | .where("bar", is(jsonInt(2))) 173 | .where("baz", is(jsonInt(3))); 174 | 175 | final ObjectNode nestedMismatches = 176 | NF.objectNode().put("foo", -1).put("bar", 2).put("baz", "was string"); 177 | 178 | final StringDescription description = new StringDescription(); 179 | sut.describeMismatch(nestedMismatches, description); 180 | 181 | assertThat( 182 | description.toString(), 183 | is( 184 | "{\n" 185 | + " \"foo\": was a number node with value that was <-1>\n" 186 | + " ...\n" 187 | + " \"baz\": was not a number node, but a string node\n" 188 | + "}")); 189 | } 190 | 191 | @Test 192 | public void multipleConsecutiveMismatchesHaveNoEllipsis() throws Exception { 193 | final Matcher sut = 194 | jsonObject() 195 | .where("foo", is(jsonInt(1))) 196 | .where("bar", is(jsonInt(2))) 197 | .where("baz", is(jsonInt(3))); 198 | 199 | final ObjectNode nestedMismatches = 200 | NF.objectNode().put("foo", -1).put("bar", "was string").put("baz", 3); 201 | 202 | final StringDescription description = new StringDescription(); 203 | sut.describeMismatch(nestedMismatches, description); 204 | 205 | assertThat( 206 | description.toString(), 207 | is( 208 | "{\n" 209 | + " \"foo\": was a number node with value that was <-1>\n" 210 | + " \"bar\": was not a number node, but a string node\n" 211 | + " ...\n" 212 | + "}")); 213 | } 214 | 215 | @Test 216 | public void nonConsecutiveMismatchesSeparatedByEllipsis() throws Exception { 217 | final Matcher sut = 218 | jsonObject() 219 | .where("foo", is(jsonInt(1))) 220 | .where("bar", is(jsonInt(2))) 221 | .where("baz", is(jsonInt(3))) 222 | .where("qux", is(jsonInt(4))) 223 | .where("quux", is(jsonInt(5))); 224 | 225 | final ObjectNode nestedMismatches = 226 | NF.objectNode().put("foo", -1).put("bar", "was string").put("baz", 3).put("quux", 5); 227 | 228 | final StringDescription description = new StringDescription(); 229 | sut.describeMismatch(nestedMismatches, description); 230 | 231 | assertThat( 232 | description.toString(), 233 | is( 234 | "{\n" 235 | + " \"foo\": was a number node with value that was <-1>\n" 236 | + " \"bar\": was not a number node, but a string node\n" 237 | + " ...\n" 238 | + " \"qux\": was not a number node, but a missing node\n" 239 | + " ...\n" 240 | + "}")); 241 | } 242 | 243 | @Test 244 | public void testMultipleMismatchesWithNestingReportsAllMismatches() throws Exception { 245 | final Matcher sut = 246 | jsonObject() 247 | .where("foo", is(jsonObject().where("val", jsonBoolean(true)))) 248 | .where("bar", is(jsonInt(2))) 249 | .where("baz", is(jsonInt(3))); 250 | 251 | final ObjectNode nestedMismatches = NF.objectNode().put("bar", "was string").put("baz", 3); 252 | nestedMismatches.set("foo", NF.objectNode().put("val", false)); 253 | 254 | final StringDescription description = new StringDescription(); 255 | sut.describeMismatch(nestedMismatches, description); 256 | 257 | assertThat( 258 | description.toString(), 259 | is( 260 | "{\n" 261 | + " \"foo\": {\n" 262 | + " \"val\": was a boolean node with value that was \n" 263 | + " }\n" 264 | + " \"bar\": was not a number node, but a string node\n" 265 | + " ...\n" 266 | + "}")); 267 | } 268 | 269 | @Test 270 | public void testDescription() throws Exception { 271 | final Matcher sut = 272 | jsonObject() 273 | .where("foo", is(jsonInt(1))) 274 | .where("bar", is(jsonBoolean(false))) 275 | .where("baz", is(jsonObject().where("foo", is(jsonNull())))); 276 | 277 | final StringDescription description = new StringDescription(); 278 | sut.describeTo(description); 279 | 280 | assertThat( 281 | description.toString(), 282 | is( 283 | "{\n" 284 | + " \"foo\": is a number node with value that is <1>\n" 285 | + " \"bar\": is a boolean node with value that is \n" 286 | + " \"baz\": is {\n" 287 | + " \"foo\": is a null node\n" 288 | + " }\n" 289 | + "}")); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonStringMatchingTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonNumber.jsonInt; 24 | import static com.spotify.hamcrest.jackson.IsJsonObject.jsonObject; 25 | import static com.spotify.hamcrest.jackson.IsJsonStringMatching.isJsonStringMatching; 26 | import static org.hamcrest.CoreMatchers.any; 27 | import static org.hamcrest.CoreMatchers.containsString; 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.hamcrest.core.Is.is; 30 | import static org.hamcrest.core.IsNot.not; 31 | 32 | import com.fasterxml.jackson.databind.JsonNode; 33 | import org.hamcrest.Description; 34 | import org.hamcrest.Matcher; 35 | import org.hamcrest.StringDescription; 36 | import org.junit.Test; 37 | 38 | public class IsJsonStringMatchingTest { 39 | 40 | @Test 41 | public void invalidJsonDoesNotMatch() throws Exception { 42 | final Matcher sut = isJsonStringMatching(any(JsonNode.class)); 43 | 44 | assertThat("{", not(sut)); 45 | } 46 | 47 | @Test 48 | public void testDescription() throws Exception { 49 | final Matcher sut = isJsonStringMatching(jsonObject()); 50 | 51 | final Description description = new StringDescription(); 52 | sut.describeTo(description); 53 | 54 | assertThat(description.toString(), is("A JSON string that matches {\n" + "}")); 55 | } 56 | 57 | @Test 58 | public void testNull() throws Exception { 59 | final Matcher sut = isJsonStringMatching(any(JsonNode.class)); 60 | 61 | assertThat(null, not(sut)); 62 | } 63 | 64 | @Test 65 | public void validJsonMatchesAnything() throws Exception { 66 | final Matcher sut = isJsonStringMatching(any(JsonNode.class)); 67 | 68 | assertThat("{}", sut); 69 | } 70 | 71 | @Test 72 | public void validJsonMatchesAnObject() throws Exception { 73 | assertThat("{}", isJsonStringMatching(jsonObject())); 74 | } 75 | 76 | @Test 77 | public void testJsonInt() throws Exception { 78 | assertThat("123", isJsonStringMatching(jsonInt(123))); 79 | } 80 | 81 | @Test 82 | public void invalidJsonDescription() throws Exception { 83 | final Matcher sut = isJsonStringMatching(any(JsonNode.class)); 84 | 85 | final Description description = new StringDescription(); 86 | sut.describeMismatch("{", description); 87 | 88 | assertThat(description.toString(), containsString("but the string was not valid JSON")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /jackson/src/test/java/com/spotify/hamcrest/jackson/IsJsonTextTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-jackson 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.jackson; 22 | 23 | import static com.spotify.hamcrest.jackson.IsJsonText.jsonText; 24 | import static org.hamcrest.Matchers.anything; 25 | import static org.hamcrest.Matchers.isEmptyOrNullString; 26 | import static org.hamcrest.Matchers.isEmptyString; 27 | import static org.hamcrest.core.Is.is; 28 | import static org.junit.Assert.assertThat; 29 | 30 | import com.fasterxml.jackson.databind.JsonNode; 31 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 32 | import org.hamcrest.Matcher; 33 | import org.hamcrest.StringDescription; 34 | import org.junit.Test; 35 | 36 | public class IsJsonTextTest { 37 | 38 | private static final JsonNodeFactory NF = JsonNodeFactory.instance; 39 | 40 | @Test 41 | public void testType() throws Exception { 42 | final Matcher sut = jsonText(); 43 | 44 | assertThat(NF.textNode("foo"), is(sut)); 45 | } 46 | 47 | @Test 48 | public void testLiteral() throws Exception { 49 | final Matcher sut = jsonText(NF.textNode("foo")); 50 | 51 | assertThat(NF.textNode("foo"), is(sut)); 52 | } 53 | 54 | @Test 55 | public void testString() throws Exception { 56 | final Matcher sut = jsonText("foo"); 57 | 58 | assertThat(NF.textNode("foo"), is(sut)); 59 | } 60 | 61 | @Test 62 | public void testIsEmptyString() throws Exception { 63 | final Matcher sut = jsonText(isEmptyString()); 64 | 65 | assertThat(NF.textNode(""), is(sut)); 66 | } 67 | 68 | @Test 69 | public void testIsEmptyOrNullString() throws Exception { 70 | final Matcher sut = jsonText(isEmptyOrNullString()); 71 | 72 | assertThat(NF.textNode(""), is(sut)); 73 | } 74 | 75 | @Test 76 | public void testMismatchElements() throws Exception { 77 | final Matcher sut = jsonText(is("a")); 78 | 79 | final StringDescription description = new StringDescription(); 80 | sut.describeMismatch(NF.textNode("foo"), description); 81 | 82 | assertThat(description.toString(), is("was a text node with value that was \"foo\"")); 83 | } 84 | 85 | @Test 86 | public void testMismatchType() throws Exception { 87 | final Matcher sut = jsonText(is("a")); 88 | 89 | final StringDescription description = new StringDescription(); 90 | sut.describeMismatch(NF.booleanNode(false), description); 91 | 92 | assertThat(description.toString(), is("was not a string node, but a boolean node")); 93 | } 94 | 95 | @Test 96 | public void testDescription() throws Exception { 97 | final Matcher sut = jsonText(is(anything())); 98 | 99 | final StringDescription description = new StringDescription(); 100 | sut.describeTo(description); 101 | 102 | assertThat(description.toString(), is("a text node with value that is ANYTHING")); 103 | } 104 | 105 | @Test 106 | public void testDescriptionForEmptyConstructor() throws Exception { 107 | final Matcher sut = jsonText(); 108 | 109 | final StringDescription description = new StringDescription(); 110 | sut.describeTo(description); 111 | 112 | assertThat(description.toString(), is("a text node with value that is ANYTHING")); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /optional/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hamcrest 5 | com.spotify 6 | 1.3.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hamcrest-optional 11 | 12 | 13 | 14 | org.hamcrest 15 | hamcrest 16 | 17 | 18 | junit 19 | junit 20 | test 21 | 22 | 23 | com.google.guava 24 | guava 25 | test 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /optional/src/main/java/com/spotify/hamcrest/optional/EmptyOptional.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import java.util.Optional; 24 | import org.hamcrest.Description; 25 | import org.hamcrest.TypeSafeDiagnosingMatcher; 26 | 27 | /** Matches an empty Optional. */ 28 | class EmptyOptional extends TypeSafeDiagnosingMatcher> { 29 | 30 | @Override 31 | protected boolean matchesSafely(final Optional item, final Description mismatchDescription) { 32 | if (item.isPresent()) { 33 | mismatchDescription.appendText("was present with ").appendValue(item.get()); 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | @Override 40 | public void describeTo(final Description description) { 41 | description.appendText("an Optional that's empty"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /optional/src/main/java/com/spotify/hamcrest/optional/OptionalMatchers.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import static org.hamcrest.CoreMatchers.anything; 24 | import static org.hamcrest.CoreMatchers.equalTo; 25 | 26 | import java.util.Optional; 27 | import org.hamcrest.Matcher; 28 | 29 | public final class OptionalMatchers { 30 | 31 | private OptionalMatchers() {} 32 | 33 | /** Creates a Matcher that matches empty Optionals. */ 34 | public static Matcher> emptyOptional() { 35 | return new EmptyOptional<>(); 36 | } 37 | 38 | /** Creates a Matcher that matches any Optional with a value. */ 39 | public static Matcher> optionalWithValue() { 40 | return optionalWithValue(anything()); 41 | } 42 | 43 | /** 44 | * Creates a Matcher that matches if an Optional contains a given value as shortcut for 45 | * optionalWithValue(equalTo(x)). 46 | */ 47 | public static Matcher> optionalWithValue(final T value) { 48 | return optionalWithValue(equalTo(value)); 49 | } 50 | 51 | /** Creates a Matcher that matches an Optional with a value that matches the given Matcher. */ 52 | public static Matcher> optionalWithValue(final Matcher matcher) { 53 | return new PresentOptional<>(matcher); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /optional/src/main/java/com/spotify/hamcrest/optional/PresentOptional.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import java.util.Optional; 24 | import org.hamcrest.Description; 25 | import org.hamcrest.Matcher; 26 | import org.hamcrest.TypeSafeDiagnosingMatcher; 27 | 28 | class PresentOptional extends TypeSafeDiagnosingMatcher> { 29 | 30 | private final Matcher matcher; 31 | 32 | PresentOptional(final Matcher matcher) { 33 | this.matcher = matcher; 34 | } 35 | 36 | @Override 37 | protected boolean matchesSafely( 38 | final Optional item, final Description mismatchDescription) { 39 | if (item.isPresent()) { 40 | if (matcher.matches(item.get())) { 41 | return true; 42 | } else { 43 | mismatchDescription.appendText("was an Optional whose value "); 44 | matcher.describeMismatch(item.get(), mismatchDescription); 45 | return false; 46 | } 47 | } else { 48 | mismatchDescription.appendText("was not present"); 49 | return false; 50 | } 51 | } 52 | 53 | @Override 54 | public void describeTo(final Description description) { 55 | description.appendText("an Optional with a value that ").appendDescriptionOf(matcher); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /optional/src/test/java/com/spotify/hamcrest/optional/EmptyOptionalTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import static org.hamcrest.CoreMatchers.is; 24 | import static org.junit.Assert.assertThat; 25 | 26 | import java.util.Optional; 27 | import org.hamcrest.Matcher; 28 | import org.hamcrest.StringDescription; 29 | import org.junit.Test; 30 | 31 | public class EmptyOptionalTest { 32 | 33 | private static final Matcher> SUT = OptionalMatchers.emptyOptional(); 34 | 35 | @Test 36 | public void testMismatchFormatting() throws Exception { 37 | final StringDescription description = new StringDescription(); 38 | SUT.describeMismatch(Optional.of(1), description); 39 | 40 | assertThat(description.toString(), is("was present with <1>")); 41 | } 42 | 43 | @Test 44 | public void testDescriptionFormatting() throws Exception { 45 | final StringDescription description = new StringDescription(); 46 | SUT.describeTo(description); 47 | 48 | assertThat(description.toString(), is("an Optional that's empty")); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /optional/src/test/java/com/spotify/hamcrest/optional/OptionalMatchersTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import static com.spotify.hamcrest.optional.OptionalMatchers.emptyOptional; 24 | import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue; 25 | import static org.hamcrest.CoreMatchers.equalTo; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.CoreMatchers.not; 28 | import static org.hamcrest.Matchers.hasSize; 29 | import static org.junit.Assert.assertThat; 30 | 31 | import java.util.Arrays; 32 | import java.util.List; 33 | import java.util.Optional; 34 | import org.junit.Test; 35 | 36 | public class OptionalMatchersTest { 37 | 38 | @Test 39 | public void testPresent() { 40 | assertThat(Optional.of("x"), optionalWithValue()); 41 | 42 | assertThat(Optional.of("x"), OptionalMatchers.optionalWithValue("x")); 43 | 44 | assertThat(Optional.of("x"), OptionalMatchers.optionalWithValue(equalTo("x"))); 45 | assertThat(Optional.of("x"), OptionalMatchers.optionalWithValue(not(equalTo("a")))); 46 | 47 | assertThat(Optional.empty(), is(emptyOptional())); 48 | } 49 | 50 | /** 51 | * Ensure that OptionalMatchers.optionalWithValue(matcher) can be used with Matchers of other 52 | * generic types. This test is really verified at compile-time and not run-time. 53 | */ 54 | @Test 55 | public void testGenerics() { 56 | final Optional> opt = Optional.of(Arrays.asList(1, 2, 3, 4)); 57 | assertThat(opt, OptionalMatchers.optionalWithValue(hasSize(4))); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /optional/src/test/java/com/spotify/hamcrest/optional/PresentOptionalTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-optional 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.optional; 22 | 23 | import static com.spotify.hamcrest.optional.OptionalMatchers.optionalWithValue; 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | import java.util.Optional; 28 | import org.hamcrest.Matcher; 29 | import org.hamcrest.StringDescription; 30 | import org.junit.Test; 31 | 32 | public class PresentOptionalTest { 33 | 34 | private static final Matcher> SUT = optionalWithValue(is(1)); 35 | 36 | @Test 37 | public void testMismatchFormatting() throws Exception { 38 | final StringDescription description = new StringDescription(); 39 | SUT.describeMismatch(Optional.empty(), description); 40 | 41 | assertThat(description.toString(), is("was not present")); 42 | } 43 | 44 | @Test 45 | public void testValueMismatchFormatting() throws Exception { 46 | final StringDescription description = new StringDescription(); 47 | SUT.describeMismatch(Optional.of(2), description); 48 | 49 | assertThat(description.toString(), is("was an Optional whose value was <2>")); 50 | } 51 | 52 | @Test 53 | public void testDescriptionFormatting() throws Exception { 54 | final StringDescription description = new StringDescription(); 55 | SUT.describeTo(description); 56 | 57 | assertThat(description.toString(), is("an Optional with a value that is <1>")); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pojo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hamcrest 5 | com.spotify 6 | 1.3.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hamcrest-pojo 11 | 12 | 13 | 14 | com.google.guava 15 | guava 16 | 17 | 18 | com.spotify 19 | hamcrest-util 20 | 21 | 22 | org.hamcrest 23 | hamcrest 24 | 25 | 26 | com.google.auto.value 27 | auto-value 28 | provided 29 | 30 | 31 | com.google.auto.value 32 | auto-value-annotations 33 | provided 34 | 35 | 36 | javax.annotation 37 | javax.annotation-api 38 | 1.3.2 39 | 40 | 41 | junit 42 | junit 43 | test 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /pojo/src/main/java/com/spotify/hamcrest/pojo/IsPojo.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-pojo 4 | * -- 5 | * Copyright (C) 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.pojo; 22 | 23 | import static java.util.Arrays.stream; 24 | import static java.util.Objects.requireNonNull; 25 | 26 | import com.google.auto.value.AutoValue; 27 | import com.google.common.base.CaseFormat; 28 | import com.google.common.base.Joiner; 29 | import com.google.common.base.Splitter; 30 | import com.google.common.collect.ImmutableMap; 31 | import com.spotify.hamcrest.util.DescriptionUtils; 32 | import java.lang.invoke.SerializedLambda; 33 | import java.lang.reflect.InvocationTargetException; 34 | import java.lang.reflect.Method; 35 | import java.security.AccessController; 36 | import java.security.PrivilegedActionException; 37 | import java.security.PrivilegedExceptionAction; 38 | import java.util.LinkedHashMap; 39 | import java.util.Map; 40 | import java.util.Optional; 41 | import java.util.function.Consumer; 42 | import org.hamcrest.Description; 43 | import org.hamcrest.Matcher; 44 | import org.hamcrest.StringDescription; 45 | import org.hamcrest.TypeSafeDiagnosingMatcher; 46 | 47 | @AutoValue 48 | public abstract class IsPojo extends TypeSafeDiagnosingMatcher { 49 | 50 | IsPojo() { 51 | // Prevent outside instantiation. 52 | } 53 | 54 | abstract Class cls(); 55 | 56 | abstract ImmutableMap> methodHandlers(); 57 | 58 | public static IsPojo pojo(final Class cls) { 59 | return builder(cls).build(); 60 | } 61 | 62 | public IsPojo where(final String methodName, final Matcher returnValueMatcher) { 63 | return where( 64 | methodName, 65 | self -> { 66 | final Method method = methodWithName(methodName, self); 67 | method.setAccessible(true); 68 | @SuppressWarnings("unchecked") 69 | final T returnValue = (T) method.invoke(self); 70 | return returnValue; 71 | }, 72 | returnValueMatcher); 73 | } 74 | 75 | public IsPojo where( 76 | final MethodReference methodReference, final Matcher returnValueMatcher) { 77 | final SerializedLambda serializedLambda = serializeLambda(methodReference); 78 | 79 | ensureDirectMethodReference(serializedLambda); 80 | 81 | return where(serializedLambda.getImplMethodName(), methodReference, returnValueMatcher); 82 | } 83 | 84 | private IsPojo where( 85 | final String methodName, 86 | final MethodReference valueExtractor, 87 | final Matcher matcher) { 88 | 89 | return toBuilder() 90 | .methodHandler(methodName, MethodHandler.create(valueExtractor, matcher)) 91 | .build(); 92 | } 93 | 94 | private Method methodWithName(String methodName, A self) throws NoSuchMethodException { 95 | try { 96 | return self.getClass().getDeclaredMethod(methodName); 97 | } catch (NoSuchMethodException e) { 98 | return self.getClass().getMethod(methodName); 99 | } 100 | } 101 | 102 | public IsPojo withProperty(String property, Matcher valueMatcher) { 103 | return where("get" + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, property), valueMatcher); 104 | } 105 | 106 | private static Builder builder(final Class cls) { 107 | return new AutoValue_IsPojo.Builder().cls(cls); 108 | } 109 | 110 | abstract Builder toBuilder(); 111 | 112 | @AutoValue.Builder 113 | abstract static class Builder { 114 | 115 | abstract Builder cls(final Class cls); 116 | 117 | abstract ImmutableMap.Builder> methodHandlersBuilder(); 118 | 119 | Builder methodHandler(final String methodName, final MethodHandler handler) { 120 | methodHandlersBuilder().put(methodName, handler); 121 | return this; 122 | } 123 | 124 | abstract IsPojo build(); 125 | } 126 | 127 | @Override 128 | protected boolean matchesSafely(A item, Description mismatchDescription) { 129 | if (!cls().isInstance(item)) { 130 | mismatchDescription.appendText("not an instance of " + cls().getName()); 131 | return false; 132 | } 133 | 134 | final Map> mismatches = new LinkedHashMap<>(); 135 | 136 | methodHandlers() 137 | .forEach( 138 | (methodName, handler) -> 139 | matchMethod(item, handler) 140 | .ifPresent( 141 | descriptionConsumer -> mismatches.put(methodName, descriptionConsumer))); 142 | 143 | if (!mismatches.isEmpty()) { 144 | mismatchDescription.appendText(cls().getSimpleName()).appendText(" "); 145 | DescriptionUtils.describeNestedMismatches( 146 | methodHandlers().keySet(), mismatchDescription, mismatches, IsPojo::describeMethod); 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | @Override 154 | public void describeTo(Description description) { 155 | description.appendText(cls().getSimpleName()).appendText(" {\n"); 156 | 157 | methodHandlers() 158 | .forEach( 159 | (methodName, handler) -> { 160 | final Matcher matcher = handler.matcher(); 161 | 162 | description.appendText(" ").appendText(methodName).appendText("(): "); 163 | 164 | Description innerDescription = new StringDescription(); 165 | matcher.describeTo(innerDescription); 166 | 167 | indentDescription(description, innerDescription); 168 | }); 169 | description.appendText("}"); 170 | } 171 | 172 | private static Optional> matchMethod( 173 | final A item, final MethodHandler handler) { 174 | final Matcher matcher = handler.matcher(); 175 | final MethodReference reference = handler.reference(); 176 | 177 | try { 178 | final Object value = reference.apply(item); 179 | if (!matcher.matches(value)) { 180 | return Optional.of(d -> matcher.describeMismatch(value, d)); 181 | } else { 182 | return Optional.empty(); 183 | } 184 | } catch (IllegalAccessException e) { 185 | return Optional.of(d -> d.appendText("not accessible")); 186 | } catch (NoSuchMethodException e) { 187 | return Optional.of(d -> d.appendText("did not exist")); 188 | } catch (InvocationTargetException e) { 189 | final Throwable cause = e.getCause(); 190 | return Optional.of( 191 | d -> 192 | d.appendText("threw an exception: ") 193 | .appendText(cause.getClass().getCanonicalName()) 194 | .appendText(": ") 195 | .appendText(cause.getMessage())); 196 | } catch (Exception e) { 197 | return Optional.of( 198 | d -> 199 | d.appendText("threw an exception: ") 200 | .appendText(e.getClass().getCanonicalName()) 201 | .appendText(": ") 202 | .appendText(e.getMessage())); 203 | } 204 | } 205 | 206 | private static void describeMethod(String name, Description description) { 207 | description.appendText(name).appendText("()"); 208 | } 209 | 210 | private void indentDescription(Description description, Description innerDescription) { 211 | description 212 | .appendText(Joiner.on("\n ").join(Splitter.on('\n').split(innerDescription.toString()))) 213 | .appendText("\n"); 214 | } 215 | 216 | /** 217 | * Method uses serialization trick to extract information about lambda, to give understandable 218 | * name in case of mismatch. 219 | * 220 | * @param lambda lambda to extract the name from 221 | * @return a serialized version of the lambda, containing useful information for introspection 222 | */ 223 | private static SerializedLambda serializeLambda(final Object lambda) { 224 | requireNonNull(lambda); 225 | 226 | final Method writeReplace; 227 | try { 228 | writeReplace = 229 | AccessController.doPrivileged( 230 | (PrivilegedExceptionAction) 231 | () -> { 232 | Method method = lambda.getClass().getDeclaredMethod("writeReplace"); 233 | method.setAccessible(true); 234 | return method; 235 | }); 236 | } catch (PrivilegedActionException e) { 237 | throw new IllegalStateException("Cannot serialize lambdas in unprivileged context", e); 238 | } 239 | 240 | try { 241 | return (SerializedLambda) writeReplace.invoke(lambda); 242 | } catch (ClassCastException | IllegalAccessException | InvocationTargetException e) { 243 | throw new IllegalArgumentException( 244 | "Could not serialize as a lambda (is it a lambda?): " + lambda, e); 245 | } 246 | } 247 | 248 | private static void ensureDirectMethodReference(final SerializedLambda serializedLambda) { 249 | try { 250 | final Class implClass = Class.forName(serializedLambda.getImplClass().replace('/', '.')); 251 | if (stream(implClass.getMethods()) 252 | .noneMatch( 253 | m -> m.getName().equals(serializedLambda.getImplMethodName()) && !m.isSynthetic())) { 254 | throw new IllegalArgumentException("The supplied lambda is not a direct method reference"); 255 | } 256 | } catch (final ClassNotFoundException e) { 257 | throw new IllegalStateException( 258 | "serializeLambda returned a SerializedLambda pointing to an invalid class", e); 259 | } 260 | } 261 | 262 | @AutoValue 263 | abstract static class MethodHandler { 264 | 265 | abstract MethodReference reference(); 266 | 267 | abstract Matcher matcher(); 268 | 269 | static MethodHandler create( 270 | final MethodReference reference, final Matcher matcher) { 271 | return new AutoValue_IsPojo_MethodHandler<>(reference, matcher); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /pojo/src/main/java/com/spotify/hamcrest/pojo/MethodReference.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-pojo 4 | * -- 5 | * Copyright (C) 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.pojo; 22 | 23 | import java.io.Serializable; 24 | 25 | /** 26 | * An interface for serializable method references. It is only valid to construct instances of this 27 | * interface with method references such as {@code Foo::bar}. 28 | */ 29 | @FunctionalInterface 30 | public interface MethodReference extends Serializable { 31 | 32 | /** 33 | * Applies this method reference to the specified owning object. 34 | * 35 | * @param self the owning object 36 | * @return the method result 37 | */ 38 | R apply(A self) throws Exception; 39 | } 40 | -------------------------------------------------------------------------------- /pojo/src/test/java/com/spotify/hamcrest/pojo/IsPojoTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-pojo 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.pojo; 22 | 23 | import static com.spotify.hamcrest.pojo.IsPojo.pojo; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.hamcrest.Matchers.both; 26 | import static org.hamcrest.Matchers.hasProperty; 27 | import static org.hamcrest.Matchers.hasSize; 28 | import static org.hamcrest.Matchers.instanceOf; 29 | import static org.hamcrest.Matchers.startsWith; 30 | import static org.hamcrest.core.Is.is; 31 | import static org.hamcrest.core.Is.isA; 32 | import static org.hamcrest.core.IsAnything.anything; 33 | 34 | import java.math.BigInteger; 35 | import org.hamcrest.StringDescription; 36 | import org.junit.Rule; 37 | import org.junit.Test; 38 | import org.junit.rules.ExpectedException; 39 | 40 | public class IsPojoTest { 41 | 42 | @Rule public final ExpectedException expectedException = ExpectedException.none(); 43 | 44 | @Test 45 | public void testWhere() throws Exception { 46 | final IsPojo sut = pojo(SomeClass.class).where("foo", is(42)); 47 | 48 | assertThat(new SomeClass(), is(sut)); 49 | } 50 | 51 | @Test 52 | public void testWhereMultiple() throws Exception { 53 | final IsPojo sut = 54 | pojo(SomeClass.class).where("foo", is(42)).where("getBar", is("bar")); 55 | 56 | assertThat(new SomeClass(), is(sut)); 57 | } 58 | 59 | @Test 60 | public void testProp() throws Exception { 61 | final IsPojo sut = pojo(SomeClass.class).withProperty("bar", is("bar")); 62 | 63 | assertThat(new SomeClass(), is(sut)); 64 | } 65 | 66 | @Test 67 | public void testNested() throws Exception { 68 | final IsPojo sut = 69 | pojo(SomeClass.class).where("baz", is(pojo(SomeClass.class).where("foo", is(42)))); 70 | 71 | assertThat(new SomeClass(), is(sut)); 72 | } 73 | 74 | @Test 75 | public void testDescriptionFormatting() throws Exception { 76 | 77 | final IsPojo sut = 78 | pojo(SomeClass.class) 79 | .where("baz", is(pojo(SomeClass.class).where("foo", is(42)))) 80 | .where("foo", is(42)) 81 | .withProperty("bar", is("bar")); 82 | 83 | final StringDescription description = new StringDescription(); 84 | sut.describeTo(description); 85 | 86 | assertThat( 87 | description.toString(), 88 | is( 89 | "SomeClass {\n" 90 | + " baz(): is SomeClass {\n" 91 | + " foo(): is <42>\n" 92 | + " }\n" 93 | + " foo(): is <42>\n" 94 | + " getBar(): is \"bar\"\n" 95 | + "}")); 96 | } 97 | 98 | @Test 99 | public void testMismatchFormatting() throws Exception { 100 | final IsPojo sut = 101 | pojo(SomeClass.class) 102 | .where("baz", is(pojo(SomeClass.class).where("foo", is(43)))) 103 | .where("foo", is(42)) 104 | .withProperty("bar", is("bar")); 105 | 106 | final StringDescription description = new StringDescription(); 107 | sut.describeMismatch(new SomeClass(), description); 108 | 109 | assertThat( 110 | description.toString(), 111 | is( 112 | "SomeClass {\n" 113 | + " baz(): SomeClass {\n" 114 | + " foo(): was <42>\n" 115 | + " }\n" 116 | + " ...\n" 117 | + "}")); 118 | } 119 | 120 | @Test 121 | public void testMismatchFormattingInOrderOfAddition() throws Exception { 122 | final IsPojo sut = 123 | pojo(SomeClass.class) 124 | .where("foo", is(41)) 125 | .where("baz", is(pojo(SomeClass.class).where("foo", is(43)))) 126 | .withProperty("bar", is("not-bar")); 127 | 128 | final StringDescription description = new StringDescription(); 129 | sut.describeMismatch(new SomeClass(), description); 130 | 131 | assertThat( 132 | description.toString(), 133 | is( 134 | "SomeClass {\n" 135 | + " foo(): was <42>\n" 136 | + " baz(): SomeClass {\n" 137 | + " foo(): was <42>\n" 138 | + " }\n" 139 | + " getBar(): was \"bar\"\n" 140 | + "}")); 141 | } 142 | 143 | @Test 144 | public void testThrowsException() throws Exception { 145 | final IsPojo sut = pojo(SomeClass.class).where("throwsException", is(anything())); 146 | 147 | final StringDescription description = new StringDescription(); 148 | sut.describeMismatch(new SomeClass(), description); 149 | 150 | assertThat( 151 | description.toString(), 152 | is( 153 | "SomeClass {\n" 154 | + " throwsException(): threw an exception: java.lang.RuntimeException: Error!\n" 155 | + "}")); 156 | } 157 | 158 | @Test 159 | public void testNoSuchMethod() throws Exception { 160 | final IsPojo sut = pojo(SomeClass.class).where("doesNotExist", is(anything())); 161 | 162 | final StringDescription description = new StringDescription(); 163 | sut.describeMismatch(new SomeClass(), description); 164 | 165 | assertThat( 166 | description.toString(), is("SomeClass {\n" + " doesNotExist(): did not exist\n" + "}")); 167 | } 168 | 169 | @Test 170 | public void testWrongType() throws Exception { 171 | final IsPojo sut = pojo(SomeClass.class).where("doesNotExist", is(anything())); 172 | 173 | final StringDescription description = new StringDescription(); 174 | sut.describeMismatch(new BigInteger("1"), description); 175 | 176 | assertThat( 177 | description.toString(), is("not an instance of com.spotify.hamcrest.pojo.SomeClass")); 178 | } 179 | 180 | @Test 181 | public void testTypeSafeMatch() throws Exception { 182 | final IsPojo sut = 183 | pojo(SomeClass.class) 184 | .where(SomeClass::foo, is(42)) 185 | .where(SomeClass::baz, is(pojo(SomeClass.class).where(SomeClass::foo, is(42)))); 186 | 187 | assertThat(new SomeClass(), is(sut)); 188 | } 189 | 190 | @Test 191 | public void testTypeSafeMismatch() throws Exception { 192 | final IsPojo sut = 193 | pojo(SomeClass.class) 194 | .where(SomeClass::foo, is(41)) 195 | .where(SomeClass::baz, is(pojo(SomeClass.class).where(SomeClass::foo, is(43)))); 196 | 197 | final StringDescription description = new StringDescription(); 198 | sut.describeMismatch(new SomeClass(), description); 199 | 200 | assertThat( 201 | description.toString(), 202 | is( 203 | "SomeClass {\n" 204 | + " foo(): was <42>\n" 205 | + " baz(): SomeClass {\n" 206 | + " foo(): was <42>\n" 207 | + " }\n" 208 | + "}")); 209 | } 210 | 211 | @Test 212 | public void testMultipleIdenticalMatches() throws Exception { 213 | expectedException.expect( 214 | both(isA(IllegalArgumentException.class)) 215 | .and(hasProperty("message", startsWith("Multiple entries with same key: ")))); 216 | 217 | pojo(SomeClass.class) 218 | .where(SomeClass::getBar, is("bar1")) 219 | .where(SomeClass::getBar, instanceOf(StringBuffer.class)); 220 | } 221 | 222 | @Test 223 | public void testNonTrivialLambdas() throws Exception { 224 | expectedException.expect( 225 | both(isA(IllegalArgumentException.class)) 226 | .and( 227 | hasProperty( 228 | "message", is("The supplied lambda is not a direct method reference")))); 229 | 230 | pojo(SomeClass.class).where(s -> s.getBar().intern(), is("bar1")); 231 | } 232 | 233 | @Test 234 | public void testPrivateMethodInClass() throws Exception { 235 | final IsPojo sut = pojo(SomeClass.class).where("privateInClass", is(true)); 236 | 237 | assertThat(new SomeClass(), is(sut)); 238 | } 239 | 240 | @Test 241 | public void testMethodInSuperClass() throws Exception { 242 | final IsPojo sut = pojo(SomeClass.class).where("methodInParent", is(true)); 243 | 244 | assertThat(new SomeClass(), is(sut)); 245 | } 246 | 247 | @Test 248 | public void testCovariantMethodOverriding() { 249 | final IsPojo sut = 250 | pojo(SomeClass.class).where(SomeClass::covariantlyOverriddenMethod, hasSize(0)); 251 | 252 | assertThat(new SomeClass(), is(sut)); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /pojo/src/test/java/com/spotify/hamcrest/pojo/SomeClass.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-pojo 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.pojo; 22 | 23 | import java.util.ArrayList; 24 | 25 | class SomeClass extends SomeParent { 26 | 27 | public int foo() { 28 | return 42; 29 | } 30 | 31 | public String getBar() { 32 | return "bar"; 33 | } 34 | 35 | public SomeClass baz() { 36 | return new SomeClass(); 37 | } 38 | 39 | private boolean privateInClass() { 40 | return true; 41 | } 42 | 43 | public String throwsException() { 44 | throw new RuntimeException("Error!"); 45 | } 46 | 47 | @Override 48 | public ArrayList covariantlyOverriddenMethod() { 49 | return new ArrayList<>(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pojo/src/test/java/com/spotify/hamcrest/pojo/SomeParent.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-pojo 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.pojo; 22 | 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | class SomeParent { 27 | public boolean methodInParent() { 28 | return true; 29 | } 30 | 31 | public List covariantlyOverriddenMethod() { 32 | return Collections.emptyList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.spotify 7 | foss-root 8 | 17 9 | 10 | 11 | hamcrest 12 | 1.3.4-SNAPSHOT 13 | pom 14 | https://github.com/spotify/java-hamcrest 15 | 16 | 17 | 1.10.2 18 | 19 | 20 | 21 | 22 | dflemstr 23 | David Flemström 24 | dflemstr@spotify.com 25 | 26 | 27 | mattnworb 28 | Matt Brown 29 | mattbrown@spotify.com 30 | 31 | 32 | davidxia 33 | David Xia 34 | dxia@spotify.com 35 | 36 | 37 | pettermahlen 38 | Petter Måhlén 39 | petter@spotify.com 40 | 41 | 42 | 43 | 44 | scm:git:https://github.com/spotify/java-hamcrest.git 45 | scm:git:git@github.com:spotify/java-hamcrest.git 46 | HEAD 47 | https://github.com/spotify/java-hamcrest 48 | 49 | 50 | 51 | jackson 52 | optional 53 | pojo 54 | util 55 | future 56 | 57 | 58 | 59 | 60 | 61 | com.fasterxml.jackson 62 | jackson-bom 63 | 2.15.2 64 | import 65 | pom 66 | 67 | 68 | com.google.auto.value 69 | auto-value 70 | ${auto-value.version} 71 | 72 | 73 | com.google.auto.value 74 | auto-value-annotations 75 | ${auto-value.version} 76 | 77 | 78 | com.google.guava 79 | guava 80 | 32.1.2-jre 81 | 82 | 83 | org.hamcrest 84 | hamcrest 85 | 2.2 86 | 87 | 88 | com.spotify 89 | hamcrest-util 90 | ${project.version} 91 | 92 | 93 | junit 94 | junit 95 | 4.13.1 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | maven-enforcer-plugin 104 | 105 | 106 | maven-failsafe-plugin 107 | 108 | 109 | org.jacoco 110 | jacoco-maven-plugin 111 | 112 | 113 | com.coveo 114 | fmt-maven-plugin 115 | 116 | 117 | 118 | 119 | 120 | ci 121 | 122 | 123 | env.CI 124 | true 125 | 126 | 127 | 128 | 129 | 130 | com.coveo 131 | fmt-maven-plugin 132 | 133 | 134 | 135 | check 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | hamcrest 5 | com.spotify 6 | 1.3.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hamcrest-util 11 | 12 | 13 | 14 | com.google.guava 15 | guava 16 | 17 | 18 | org.hamcrest 19 | hamcrest 20 | 21 | 22 | junit 23 | junit 24 | test 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /util/src/main/java/com/spotify/hamcrest/util/DescriptionUtils.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-util 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.util; 22 | 23 | import static com.google.common.base.Preconditions.checkArgument; 24 | 25 | import com.google.common.base.Joiner; 26 | import com.google.common.base.Splitter; 27 | import java.util.Map; 28 | import java.util.Objects; 29 | import java.util.Set; 30 | import java.util.function.BiConsumer; 31 | import java.util.function.Consumer; 32 | import org.hamcrest.Description; 33 | import org.hamcrest.StringDescription; 34 | 35 | /** 36 | * Utils class to help fill {@link Description}. 37 | * 38 | * @see #describeNestedMismatches(Set, Description, Map, BiConsumer) 39 | * @see #indentDescription(Description, Description) 40 | */ 41 | public final class DescriptionUtils { 42 | 43 | private static final Splitter LINE_SPLITTER = Splitter.on('\n'); 44 | private static final Joiner INDENTED_LINE_JOINER = Joiner.on("\n "); 45 | 46 | private DescriptionUtils() { 47 | throw new IllegalAccessError("This class may not be instantiated."); 48 | } 49 | 50 | /** 51 | * Idents a description. 52 | * 53 | * @param description the current active description. 54 | * @param innerDescription the description we want indented. 55 | */ 56 | public static void indentDescription(Description description, Description innerDescription) { 57 | final Iterable lines = LINE_SPLITTER.split(innerDescription.toString().trim()); 58 | final String indentedLines = INDENTED_LINE_JOINER.join(lines); 59 | description.appendText(indentedLines).appendText("\n"); 60 | } 61 | 62 | /** 63 | * Describes a nested mismatch, useful for nested types like Objects or Maps 64 | * 65 | *

This will print all mismatches occurring in a mismatch list, and properly handle 66 | * ellipsis (...). Order will also be maintained based on the order of the allKeys set. To 67 | * maintain input order, consider using {@link java.util.LinkedHashSet} or {@link 68 | * java.util.LinkedHashMap} 69 | * 70 | *

This will also handle proper indentation in the case of nesting. Description will contain 71 | * output that looks like, 72 | * 73 | *

{@code
 74 |    * {
 75 |    *   ...
 76 |    *   myKey: expected 1 but was 2
 77 |    *   myOtherKey: expected "hello" but was "world"
 78 |    *   ...
 79 |    *   nestingKey: {
 80 |    *     nestedKey: expected null
 81 |    *   }
 82 |    * }
 83 |    *
 84 |    * }
85 | * 86 | * @param allKeys {@link Set} of all keys expecting to match 87 | * @param mismatchDescription The {@link Description} to write the output to 88 | * @param mismatchedKeys A {@link Map} of all keys mismatched. The value is a {@link Consumer} 89 | * which will write the describe the mismatch for that key 90 | * @param describeKey A {@link BiConsumer} used to describe the key 91 | */ 92 | public static void describeNestedMismatches( 93 | Set allKeys, 94 | Description mismatchDescription, 95 | Map> mismatchedKeys, 96 | BiConsumer describeKey) { 97 | checkArgument(!mismatchedKeys.isEmpty(), "mismatchKeys must not be empty"); 98 | String previousMismatchKey = null; 99 | String previousKey = null; 100 | 101 | mismatchDescription.appendText("{\n"); 102 | 103 | for (String key : allKeys) { 104 | if (mismatchedKeys.containsKey(key)) { 105 | // If this is not the first key and the previous key was not a mismatch then add ellipsis 106 | if (previousKey != null && !Objects.equals(previousMismatchKey, previousKey)) { 107 | mismatchDescription.appendText(" ...\n"); 108 | } 109 | 110 | describeMismatchForKey(key, mismatchDescription, describeKey, mismatchedKeys.get(key)); 111 | previousMismatchKey = key; 112 | } 113 | previousKey = key; 114 | } 115 | 116 | // If the last element was not a mismatch then add ellipsis 117 | if (!Objects.equals(previousMismatchKey, previousKey)) { 118 | mismatchDescription.appendText(" ...\n"); 119 | } 120 | 121 | mismatchDescription.appendText("}"); 122 | } 123 | 124 | private static void describeMismatchForKey( 125 | String key, 126 | Description mismatchDescription, 127 | BiConsumer describeKey, 128 | Consumer innerAction) { 129 | 130 | mismatchDescription.appendText(" "); 131 | describeKey.accept(String.valueOf(key), mismatchDescription); 132 | mismatchDescription.appendText(": "); 133 | 134 | final Description innerDescription = new StringDescription(); 135 | innerAction.accept(innerDescription); 136 | indentDescription(mismatchDescription, innerDescription); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /util/src/main/java/com/spotify/hamcrest/util/LanguageUtils.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-util 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.util; 22 | 23 | /** 24 | * Utils modifying strings with language. 25 | * 26 | *

Currently only supports english. 27 | * 28 | * @see #addArticle(String) 29 | */ 30 | public final class LanguageUtils { 31 | 32 | private LanguageUtils() { 33 | throw new IllegalAccessError("This class may not be instantiated."); 34 | } 35 | 36 | /** 37 | * Adds a `a` or `an` article to a given {@param word}. 38 | * 39 | * @param word the word we want to add the article to. 40 | * @return word with article. 41 | */ 42 | public static String addArticle(final String word) { 43 | if (word.isEmpty()) { 44 | return ""; 45 | } 46 | switch (word.charAt(0)) { 47 | case 'a': 48 | case 'e': 49 | case 'i': 50 | case 'o': 51 | return "an " + word; 52 | default: 53 | return "a " + word; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /util/src/test/java/com/spotify/hamcrest/util/DescriptionUtilsTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-util 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.util; 22 | 23 | import static java.util.Arrays.asList; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.hamcrest.core.Is.is; 26 | 27 | import com.google.common.collect.ImmutableMap; 28 | import java.util.LinkedHashSet; 29 | import java.util.Map; 30 | import java.util.Set; 31 | import java.util.function.BiConsumer; 32 | import java.util.function.Consumer; 33 | import org.hamcrest.Description; 34 | import org.hamcrest.StringDescription; 35 | import org.junit.Test; 36 | 37 | public class DescriptionUtilsTest { 38 | 39 | @Test 40 | public void testIndentDescription() throws Exception { 41 | StringDescription innerDescription = new StringDescription(); 42 | innerDescription.appendText("a\nb"); 43 | 44 | StringDescription description = new StringDescription(); 45 | DescriptionUtils.indentDescription(description, innerDescription); 46 | 47 | assertThat(description.toString(), is("a\n b\n")); 48 | } 49 | 50 | @Test 51 | public void testIndentDescriptionNoExtraNewline() throws Exception { 52 | StringDescription innerDescription = new StringDescription(); 53 | innerDescription.appendText("a\nb\n"); 54 | 55 | StringDescription description = new StringDescription(); 56 | DescriptionUtils.indentDescription(description, innerDescription); 57 | 58 | assertThat(description.toString(), is("a\n b\n")); 59 | } 60 | 61 | @Test 62 | public void describeNestedMismatchesNoEllipsisBeforeFirstValue() throws Exception { 63 | Set allKeys = new LinkedHashSet<>(asList("first", "second", "third")); 64 | StringDescription description = new StringDescription(); 65 | Map> mismatchedKeys = 66 | ImmutableMap.of("first", desc -> desc.appendText("mismatch!")); 67 | BiConsumer describeKey = (str, desc) -> desc.appendText(str); 68 | 69 | DescriptionUtils.describeNestedMismatches(allKeys, description, mismatchedKeys, describeKey); 70 | 71 | assertThat(description.toString(), is("{\n" + " first: mismatch!\n" + " ...\n" + "}")); 72 | } 73 | 74 | @Test 75 | public void describeMismatchesNoEllipsisAfterLastValue() throws Exception { 76 | Set allKeys = new LinkedHashSet<>(asList("first", "second", "third")); 77 | StringDescription description = new StringDescription(); 78 | Map> mismatchedKeys = 79 | ImmutableMap.of("third", desc -> desc.appendText("mismatch!")); 80 | BiConsumer describeKey = (str, desc) -> desc.appendText(str); 81 | 82 | DescriptionUtils.describeNestedMismatches(allKeys, description, mismatchedKeys, describeKey); 83 | 84 | assertThat(description.toString(), is("{\n" + " ...\n" + " third: mismatch!\n" + "}")); 85 | } 86 | 87 | @Test 88 | public void describeNestedMismatchesEllipsisBeforeAndAfterAMiddleElement() throws Exception { 89 | Set allKeys = new LinkedHashSet<>(asList("first", "second", "third")); 90 | StringDescription description = new StringDescription(); 91 | Map> mismatchedKeys = 92 | ImmutableMap.of("second", desc -> desc.appendText("mismatch!")); 93 | BiConsumer describeKey = (str, desc) -> desc.appendText(str); 94 | 95 | DescriptionUtils.describeNestedMismatches(allKeys, description, mismatchedKeys, describeKey); 96 | 97 | assertThat( 98 | description.toString(), is("{\n" + " ...\n" + " second: mismatch!\n" + " ...\n" + "}")); 99 | } 100 | 101 | @Test 102 | public void describeNestMismatchesNoEllipsisBetweenConsecutiveMismatches() throws Exception { 103 | Set allKeys = new LinkedHashSet<>(asList("first", "second", "third", "forth")); 104 | StringDescription description = new StringDescription(); 105 | Map> mismatchedKeys = 106 | ImmutableMap.of( 107 | "second", desc -> desc.appendText("mismatch!"), 108 | "third", desc -> desc.appendText("mismatch!")); 109 | BiConsumer describeKey = (str, desc) -> desc.appendText(str); 110 | 111 | DescriptionUtils.describeNestedMismatches(allKeys, description, mismatchedKeys, describeKey); 112 | 113 | assertThat( 114 | description.toString(), 115 | is("{\n" + " ...\n" + " second: mismatch!\n" + " third: mismatch!\n" + " ...\n" + "}")); 116 | } 117 | 118 | @Test 119 | public void describeNestedMismatchesProperlyIndentsNestedMismatch() throws Exception { 120 | Set allKeys = new LinkedHashSet<>(asList("first", "second", "third")); 121 | StringDescription description = new StringDescription(); 122 | Map> mismatchedKeys = 123 | ImmutableMap.of("second", desc -> desc.appendText("{\n nestedKey: mismatch!\n}")); 124 | BiConsumer describeKey = (str, desc) -> desc.appendText(str); 125 | 126 | DescriptionUtils.describeNestedMismatches(allKeys, description, mismatchedKeys, describeKey); 127 | 128 | assertThat( 129 | description.toString(), 130 | is( 131 | "{\n" 132 | + " ...\n" 133 | + " second: {\n" 134 | + " nestedKey: mismatch!\n" 135 | + " }\n" 136 | + " ...\n" 137 | + "}")); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /util/src/test/java/com/spotify/hamcrest/util/LanguageUtilsTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * hamcrest-util 4 | * -- 5 | * Copyright (C) 2016 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.hamcrest.util; 22 | 23 | import static org.hamcrest.core.Is.is; 24 | import static org.junit.Assert.assertThat; 25 | 26 | import org.junit.Test; 27 | 28 | public class LanguageUtilsTest { 29 | 30 | @Test 31 | public void testAddArticleEmpty() throws Exception { 32 | assertThat(LanguageUtils.addArticle(""), is("")); 33 | } 34 | 35 | @Test 36 | public void testAddArticleVowel() throws Exception { 37 | assertThat(LanguageUtils.addArticle("attitude"), is("an attitude")); 38 | assertThat(LanguageUtils.addArticle("ear"), is("an ear")); 39 | assertThat(LanguageUtils.addArticle("igloo"), is("an igloo")); 40 | assertThat(LanguageUtils.addArticle("oar"), is("an oar")); 41 | } 42 | 43 | @Test 44 | public void testAddArticleConsonant() throws Exception { 45 | assertThat(LanguageUtils.addArticle("tower"), is("a tower")); 46 | assertThat(LanguageUtils.addArticle("sibling"), is("a sibling")); 47 | assertThat(LanguageUtils.addArticle("rotary dish"), is("a rotary dish")); 48 | assertThat(LanguageUtils.addArticle("user"), is("a user")); 49 | } 50 | } 51 | --------------------------------------------------------------------------------