diff --git a/sportsnet-reactive-consumer/README.md b/sportsnet-reactive-consumer/README.md index 356be6d..016a2d5 100644 --- a/sportsnet-reactive-consumer/README.md +++ b/sportsnet-reactive-consumer/README.md @@ -278,6 +278,8 @@ Content-Type: [application/json;charset=UTF-8] * [SourceCode to this example](https://github.com/marios-code-path/bootiful-testing/tree/master/sportsnet-reactive-consumer) +* [Ham Vocke's Test Pyramid on Fowler.com](https://martinfowler.com/articles/practical-test-pyramid.htm) + * [WebTestClient Documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/pdf/testing-webtestclient.pdf) * [Spring Cloud Contract DSL](https://cloud.spring.io/spring-cloud-contract/multi/multi__contract_dsl.html) diff --git a/sportsnet-reactive-consumer/src/main/java/com/example/sportsnet/SportsNetClient.java b/sportsnet-reactive-consumer/src/main/java/com/example/sportsnet/SportsNetClient.java index fcd3ecb..f547ccd 100644 --- a/sportsnet-reactive-consumer/src/main/java/com/example/sportsnet/SportsNetClient.java +++ b/sportsnet-reactive-consumer/src/main/java/com/example/sportsnet/SportsNetClient.java @@ -7,7 +7,7 @@ @Component public class SportsNetClient { - @Value("${server.url:http://localhost:8089/teams}") + @Value("${server.url:http://localhost:8089}") String baseUri; private final WebClient webClient; diff --git a/sportsnet-reactive-consumer/src/test/java/com/example/sportsnet/SportsNetClientContractTests.java b/sportsnet-reactive-consumer/src/test/java/com/example/sportsnet/SportsNetClientContractTests.java index 7b2928f..c4e897b 100644 --- a/sportsnet-reactive-consumer/src/test/java/com/example/sportsnet/SportsNetClientContractTests.java +++ b/sportsnet-reactive-consumer/src/test/java/com/example/sportsnet/SportsNetClientContractTests.java @@ -15,7 +15,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @RunWith(SpringRunner.class) @Import({SportsNetClientApp.class, SportsNetClient.class}) -@AutoConfigureStubRunner(ids = "com.example.sportsnet:reactive-producer:+:8089", +@AutoConfigureStubRunner(ids = "com.example:sportsnet-producer:+:8089", stubsMode = StubRunnerProperties.StubsMode.LOCAL) public class SportsNetClientContractTests { @Autowired @@ -26,8 +26,8 @@ public void testShouldFetchTeams() { Flux customers = this.client.getAllTeams(); StepVerifier .create(customers) - .expectNext(new Team("1", "REDS")) - .expectNext(new Team("2", "BLUES")) + .expectNext(new Team("1234", "REDSOX")) + .expectNext(new Team("2345", "RAIDERS")) .verifyComplete(); } diff --git a/sportsnet-reactive-producer/README.md b/sportsnet-reactive-producer/README.md index 2c31c99..d0d1d34 100644 --- a/sportsnet-reactive-producer/README.md +++ b/sportsnet-reactive-producer/README.md @@ -494,6 +494,8 @@ Running `mvn clean install` with a passing verification will also publish an art * [Source Code to this Demo](https://github.com/marios-code-path/bootiful-testing/tree/master/sportsnet-reactive-producer) +* [Ham Vocke's Test Pyramid on Fowler.com](https://martinfowler.com/articles/practical-test-pyramid.htm) + * [WebTestClient Documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/pdf/testing-webtestclient.pdf) * [Spring Cloud Contract DSL](https://cloud.spring.io/spring-cloud-contract/multi/multi__contract_dsl.html) @@ -502,4 +504,4 @@ Running `mvn clean install` with a passing verification will also publish an art * Maps are [Injective Functions](https://en.wikipedia.org/wiki/Injective_function) f(x) -> y for any value of x and y -* Are pretty [Constructable numbers](http://www.cut-the-knot.org/arithmetic/constructibleExamples.shtml) interesting +* GIST FOR [bwaldvogel.mongo.reactive](https://gist.github.com/Mario5Gray/f541f06e71d22cbad45c0aab04ceaca5) to configure an embedded mongo instance thats truely in-memory. diff --git a/sportsnet-reactive-producer/src/main/java/com/sportsnet/SportsNetWebConfig.java b/sportsnet-reactive-producer/src/main/java/com/sportsnet/SportsNetWebConfig.java index f447999..e2914cd 100644 --- a/sportsnet-reactive-producer/src/main/java/com/sportsnet/SportsNetWebConfig.java +++ b/sportsnet-reactive-producer/src/main/java/com/sportsnet/SportsNetWebConfig.java @@ -11,31 +11,21 @@ @Configuration public class SportsNetWebConfig { + @Bean RouterFunction routes(TeamRepository repo) { return route(GET("/teams/all"), req -> ServerResponse .ok() - .body(repo.findAll(), Team.class) - ) + .body(repo.findAll(), Team.class)) .and(route(GET("/teams/favorites"), req -> ServerResponse .ok() - .body(repo.getMyFavorites(), Team.class)) - ) + .body(repo.getMyFavorites(), Team.class))) .and(route(GET("/teams/byName"), req -> ServerResponse .ok() .body(repo.findByName(req.queryParam("name").orElse("NONE")), Team.class))); } - - // Require when executing stand-alone only! -// // @Bean -// ApplicationRunner runner(TeamRepository cr) { -// return args -> -// cr -// .saveAll(Flux.just(new Team(UUID.randomUUID().toString(), "REDS"), new Team(UUID.randomUUID().toString(), "BLUES"))) -// .subscribe(d -> System.out.println("saved " + d.toString())); -// } } diff --git a/sportsnet-reactive-producer/src/main/java/com/sportsnet/Team.java b/sportsnet-reactive-producer/src/main/java/com/sportsnet/Team.java index 75b5467..280b19d 100644 --- a/sportsnet-reactive-producer/src/main/java/com/sportsnet/Team.java +++ b/sportsnet-reactive-producer/src/main/java/com/sportsnet/Team.java @@ -2,17 +2,16 @@ import lombok.AllArgsConstructor; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; + @Data @AllArgsConstructor @NoArgsConstructor -@EqualsAndHashCode public class Team { @Id private String id; private String name; } + diff --git a/sportsnet-reactive-producer/src/test/java/com/sportsnet/BaseClass.java b/sportsnet-reactive-producer/src/test/java/com/sportsnet/BaseClass.java index 6291641..4c5703a 100644 --- a/sportsnet-reactive-producer/src/test/java/com/sportsnet/BaseClass.java +++ b/sportsnet-reactive-producer/src/test/java/com/sportsnet/BaseClass.java @@ -13,7 +13,7 @@ import reactor.core.publisher.Flux; @RunWith(SpringRunner.class) -@SpringBootTest(properties = {"server.port=0"}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BaseClass { @LocalServerPort @@ -38,7 +38,7 @@ public void before() { Mockito .when(this.repository.getMyFavorites()) - .thenReturn(Flux.just(new Team("1883", "Dodgers"))); + .thenReturn(Flux.just(new Team("1912", "RedSox"))); } } \ No newline at end of file diff --git a/sportsnet-reactive-producer/src/test/java/com/sportsnet/SportsNetWebTest.java b/sportsnet-reactive-producer/src/test/java/com/sportsnet/SportsNetWebTest.java index 6363e6d..2d1610c 100644 --- a/sportsnet-reactive-producer/src/test/java/com/sportsnet/SportsNetWebTest.java +++ b/sportsnet-reactive-producer/src/test/java/com/sportsnet/SportsNetWebTest.java @@ -4,12 +4,13 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Hooks; @@ -17,12 +18,16 @@ import reactor.test.StepVerifier; @WebFluxTest -@RunWith(MockitoJUnitRunner.class) +@Import(SportsNetWebConfig.class) +@RunWith(SpringRunner.class) public class SportsNetWebTest { private SportsNetWebConfig webConfig = new SportsNetWebConfig(); - @Mock + @Autowired + private WebTestClient webTestClient; + + @MockBean private TeamRepository repository; private Team red = new Team("1883", "Dodgers"); @@ -48,9 +53,7 @@ public void before() { @Test public void testShouldGetAll() { - WebTestClient - .bindToRouterFunction(webConfig.routes(repository)) - .build() + this.webTestClient .get().uri("/teams/all") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange() @@ -64,9 +67,9 @@ public void testShouldGetAll() { } @Test - public void testShouldBetByName() throws JsonProcessingException { + public void testShouldFetchByName() throws JsonProcessingException { - String jsonBlob = "{name:'RedSox', id: '1912'}"; + String jsonBlob = "{id: '1912', name:'RedSox'}"; WebTestClient .bindToRouterFunction(webConfig.routes(repository)) @@ -94,7 +97,6 @@ public void testShouldGetFavs() { StepVerifier .create(webResponseTeams) - .expectSubscription() .expectNext(new Team("1912", "RedSox")) .expectNextCount(0) // because why not? .verifyComplete(); diff --git a/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamRepositoryTest.java b/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamRepositoryTest.java index f481607..04d3bc7 100644 --- a/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamRepositoryTest.java +++ b/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamRepositoryTest.java @@ -1,5 +1,6 @@ package com.sportsnet; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -9,6 +10,7 @@ import org.springframework.test.context.junit4.SpringRunner; import reactor.core.publisher.Flux; import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; @@ -21,8 +23,8 @@ public class TeamRepositoryTest { @Autowired private TeamRepository repo; - private final Team one = new Team("1883", "Dodgers"); - private final Team two = new Team("1912", "RedSox"); + private final Team dodgers = new Team("1883", "Dodgers"); + private final Team redsox = new Team("1912", "RedSox"); @Before public void enableFluxDebug() { @@ -36,7 +38,7 @@ public void testShouldFetchByName() { this.repo .deleteAll() .checkpoint("saveAllTeams") - .thenMany(this.repo.saveAll(Flux.just(this.one, this.two))); + .thenMany(this.repo.saveAll(Flux.just(this.dodgers, this.redsox))); Publisher find = this.repo.findByName("Dodgers"); @@ -46,7 +48,7 @@ public void testShouldFetchByName() { StepVerifier .create(composite) - .expectNext(this.one) + .expectNext(this.dodgers) .verifyComplete(); } @@ -55,7 +57,7 @@ public void testShouldFetchFavorites() { Publisher setup = this.repo .deleteAll() - .thenMany(this.repo.saveAll(Flux.just(this.one, this.two))); + .thenMany(this.repo.saveAll(Flux.just(this.dodgers, this.redsox))); Publisher find = this.repo.getMyFavorites(); @@ -65,7 +67,7 @@ public void testShouldFetchFavorites() { StepVerifier .create(composite) - .expectNext(this.one, this.two) + .expectNext(this.dodgers, this.redsox) .verifyComplete(); } @@ -74,17 +76,20 @@ public void testFindAllWithVirtualTime() { Supplier> setupSupplier = () -> this.repo .deleteAll() - .checkpoint("MYCHECKPOINT") - .thenMany(this.repo.saveAll(Flux.just(this.one, this.two))) +// .then(Mono.error(new Exception("UHOH"))) +// .checkpoint("MYCHECKPOINT") + .thenMany(this.repo.saveAll(Flux.just(this.dodgers, this.redsox))) .thenMany(repo.findAll()) .delayElements(Duration.ofSeconds(5)); - StepVerifier.withVirtualTime(setupSupplier) - .thenAwait(Duration.ofSeconds(5)) // t = 5 + StepVerifier + .withVirtualTime(setupSupplier) + .expectSubscription() + .expectNoEvent(Duration.ofSeconds(5)) .expectNextMatches(team -> team.getName().equalsIgnoreCase("dodgers")) .thenAwait(Duration.ofSeconds(5)) // t = 10 .expectNextMatches(team -> team.getName().equalsIgnoreCase("redsox")) .expectComplete() - .verify(); + .verify(Duration.ofSeconds(5)); } } \ No newline at end of file diff --git a/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamTest.java b/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamTest.java index a3a3a3d..d7e8aee 100644 --- a/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamTest.java +++ b/sportsnet-reactive-producer/src/test/java/com/sportsnet/TeamTest.java @@ -6,6 +6,7 @@ import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; +import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; @@ -16,7 +17,9 @@ import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -27,7 +30,6 @@ public class TeamTest { private final Team red = new Team("1912", "RedSox"); private final Team blue = new Team("1883", "Dodgers"); - @Test public void testShouldCreate() { @@ -46,19 +48,22 @@ public void testShouldCreate() { .isEqualToIgnoringCase("redsox"); } + private final List teamNames = Arrays.asList( + "RedSox", + "Dodgers", + "Padres", + "Angels" + ); + @Test - public void testFluxShouldOperate() { // Dear - give me functional assertions - Flux teamFlux = Flux.just(red) - .map(t -> blue); + public void testFluxShouldExecWith() { + Flux teamFlux = Flux.just(red, blue); - StepVerifier - .create(teamFlux) - .consumeNextWith(t -> Assertions.assertThat(t) - .as("blue team instead") - .isEqualTo(blue) - ) - .expectComplete() - .verify(); + StepVerifier. + create(teamFlux) + .expectNext(red) + .expectNext(blue) + .verifyComplete(); } } \ No newline at end of file diff --git a/sportsnet-reactive-producer/src/test/resources/contracts/FavTeams.json b/sportsnet-reactive-producer/src/test/resources/contracts/FavTeams.json index 7adb750..7e895d4 100644 --- a/sportsnet-reactive-producer/src/test/resources/contracts/FavTeams.json +++ b/sportsnet-reactive-producer/src/test/resources/contracts/FavTeams.json @@ -1 +1 @@ -{"id":"1883", "name":"Dodgers"} +{"id":"1912", "name":"RedSox"} diff --git a/sportsnet-reactive-producer/src/test/resources/contracts/shouldReturnAllTeams.groovy b/sportsnet-reactive-producer/src/test/resources/contracts/shouldReturnAllTeams.groovy index f81061f..4e422be 100644 --- a/sportsnet-reactive-producer/src/test/resources/contracts/shouldReturnAllTeams.groovy +++ b/sportsnet-reactive-producer/src/test/resources/contracts/shouldReturnAllTeams.groovy @@ -12,13 +12,13 @@ Contract.make { """ [ { "id": "1883", "name" : "Dodgers" }, - { "id": "1912", "name" : "RedSox" } + { "id": "1912", "name" : "OldSox" } ] """ ) status(200) headers { - contentType(applicationJsonUtf8()) + contentType(applicationJsonUtf8()) } } } \ No newline at end of file