This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch CAUSEWAY-2873 in repository https://gitbox.apache.org/repos/asf/causeway.git
commit ea86cd2a236d68564db6536936e83399792fb992 Author: Dan Haywood <[email protected]> AuthorDate: Sun May 26 23:16:56 2024 +0100 CAUSEWAY-2873: 09-01 but skipped 08 accidentally --- .../petclinic/pages/090-integration-testing.adoc | 119 +++++++++------------ 1 file changed, 51 insertions(+), 68 deletions(-) diff --git a/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc b/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc index 425257ee68..b79e5e6e01 100644 --- a/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc +++ b/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc @@ -4,8 +4,9 @@ In an earlier section of this tutorial we looked at unit testing, but integration tests are at least as important, probably more so, as they exercise the entire application from an end-users perspective, rather than an individual part. -Integration tests are _not_ written using Selenium or similar, so avoid the fragility and maintenance effort that such tests often entail. +We don't write integration tests using Selenium or similar, and so avoid the fragility and maintenance effort that such tests often entail. Instead, the framework provides the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory] domain service which simulates the user interface in a type-safe way. +Another term sometimes used is _subcutaneous_ testing; we execute the test "under the skin". [#exercise-9-1-testing-bookvisit-using-an-integtest] @@ -24,29 +25,35 @@ mvn clean package jetty:run === Tasks -* in the `pom.xml` of the visits module, add the following dependency: +* in the `pom.xml` of the visits module, add the following dependencies: + [source,xml] .module-visits/pom.xml ---- <dependency> - <groupId>org.apache.causeway.mavendeps</groupId> - <artifactId>causeway-mavendeps-integtests</artifactId> - <type>pom</type> + <groupId>org.apache.causeway.testing</groupId> + <artifactId>causeway-testing-integtestsupport-applib</artifactId> + <scope>test</scope> +</dependency> + +<dependency> + <groupId>org.apache.causeway.persistence</groupId> + <artifactId>causeway-persistence-jpa-eclipselink</artifactId> <scope>test</scope> </dependency> ---- -* add an abstract class `VisitsModuleIntegTestAbstract` for the `visits` module, for other integ tests to subclass: + +* add an abstract class `VisitModuleIntegTestAbstract` for the `visits` module, for other integ tests to subclass: + [source,java] -.VisitsModuleIntegTestAbstract.java +.VisitModuleIntegTestAbstract.java ---- @SpringBootTest( - classes = VisitsModuleIntegTestAbstract.TestApp.class + classes = VisitModuleIntegTestAbstract.TestApp.class ) @ActiveProfiles("test") -public abstract class VisitsModuleIntegTestAbstract +public abstract class VisitModuleIntegTestAbstract extends CausewayIntegrationTestAbstractWithFixtures { @SpringBootConfiguration @@ -58,7 +65,7 @@ public abstract class VisitsModuleIntegTestAbstract CausewayModulePersistenceJpaEclipselink.class, CausewayModuleTestingFixturesApplib.class, - VisitsModule.class + VisitModule.class // <.> }) @PropertySources({ @PropertySource(CausewayPresets.H2InMemory_withUniqueSchema), @@ -68,16 +75,17 @@ public abstract class VisitsModuleIntegTestAbstract } } ---- +<.> Most of this class is boilerplate, but it does reference the module under test. -* also update the `application-test.yml` file for the `visits` module, to ensure that the database schemas for both modules are created: +* also update the `application-test.yml` file for the `visit` module, to ensure that the database schemas for both modules are created: + [source,yaml] -.module-visits/src/test/resources/application-test.yml +.module-visit/src/test/resources/application-test.yml ---- causeway: persistence: schema: - auto-create-schemas: pets,visits + auto-create-schemas: petowner,visit ---- * add a class `Bootstrap_IntegTest` integration test, inheriting from the `VisitsModuleIntegTestAbstract: @@ -85,7 +93,7 @@ causeway: [source,java] .Bootstrap_IntegTest.java ---- -public class Bootstrap_IntegTest extends VisitsModuleIntegTestAbstract { +public class Bootstrap_IntegTest extends VisitModuleIntegTestAbstract { @Test public void checks_can_bootstrap() {} @@ -97,42 +105,46 @@ Make sure this test runs and passes in both the IDE and using "mvn clean install Now we can write our actual test: -* Now add a class `Pet_bookVisit_IntegTest` integration test, also inheriting from the `VisitsModuleIntegTestAbstract: +* Now add a class `PetOwner_bookVisit_IntegTest` integration test, also inheriting from the `VisitModuleIntegTestAbstract: + [source,java] -.Pet_bookVisit_IntegTest.java +.PetOwner_bookVisit_IntegTest.java ---- -public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract { +public class PetOwner_bookVisit_IntegTest extends VisitModuleIntegTestAbstract { @BeforeEach void setup() { - fixtureScripts.run(new Pet_persona.PersistAll()); // <.> + fixtureScripts.run(new PetOwner_persona.PersistAll()); // <.> } @Test public void happy_case() { // given - Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class) // <.> - .findUsing(serviceRegistry); - List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet); + PetOwner somePetOwner = fakeDataService.enums() // <.> + .anyOf(PetOwner_persona.class) + .findUsing(serviceRegistry); + Pet somePet = fakeDataService.collections() + .anyOf(somePetOwner.getPets()); + + List<Visit> before = visitRepository.findByPetOwner(somePetOwner); + assertThat(before).isEmpty(); // when - LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime() // <.> - .plusDays(fakeDataService.ints().between(1, 3)); - String reason = fakeDataService.strings().upper(40); // <3> + LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime() // <.> + .plusDays(fakeDataService.ints().between(1, 3)); - wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, reason); // <.> + wrapMixin(PetOwner_bookVisit.class, somePetOwner).act(somePet, visitAt); // <.> // then - List<Visit> after = visitRepository.findByPetOrderByVisitAtDesc(somePet); - after.removeAll(before); - assertThat(after).hasSize(1); // <.> + List<Visit> after = visitRepository.findByPetOwner(somePetOwner); + assertThat(after).hasSize(1); + Visit visit = after.get(0); - assertThat(visit.getPet()).isSameAs(somePet); // <.> - assertThat(visit.getVisitAt()).isEqualTo(visitAt); // <6> - assertThat(visit.getReason()).isEqualTo(reason); // <6> + assertThat(visit.getPet()).isSameAs(somePet); // <.> + assertThat(visit.getPet().getPetOwner()).isSameAs(somePetOwner); // <.> + assertThat(visit.getVisitAt()).isEqualTo(visitAt); // <6> } @Inject FakeDataService fakeDataService; @@ -142,7 +154,7 @@ public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract { } ---- <.> uses same fixture script used for prototyping to set up ``Pet``s and their ``PetOwner``s. -<.> uses the xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] to select a `Pet` persona at random and uses that person to look up the corresponding domain object. +<.> uses the xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] to select a random `PetOwner` and corresponding `Pet` <.> sets up some randomised but valid argument values <.> invokes the action, using the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory] to simulate the UI <.> asserts that one new `Visit` has been created for the `Pet`. @@ -150,58 +162,29 @@ public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract { + Run the test and check that it passes. -* write an error scenario which checks that a reason has been provided: -+ -[source,java] -.Pet_bookVisit_IntegTest.java ----- -@Test -public void reason_is_required() { - - // given - Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class) - .findUsing(serviceRegistry); - List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet); - - // when, then - LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime() - .plusDays(fakeDataService.ints().between(1, 3)); - - assertThatThrownBy(() -> - wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, null) - ) - .isInstanceOf(InvalidException.class) - .hasMessage("'Reason' is mandatory"); -} ----- * write an error scenario which checks that the `visitAt` date cannot be in the past: + [source,java] -.Pet_bookVisit_IntegTest.java +.PetOwner_bookVisit_IntegTest.java ---- @Test public void cannot_book_in_the_past() { // given - Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class) + PetOwner somePetOwner = fakeDataService.enums() + .anyOf(PetOwner_persona.class) .findUsing(serviceRegistry); - List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet); + Pet somePet = fakeDataService.collections() + .anyOf(somePetOwner.getPets()); // when, then LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime(); - String reason = fakeDataService.strings().upper(40); assertThatThrownBy(() -> - wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, reason) + wrapMixin(PetOwner_bookVisit.class, somePetOwner).act(somePet, visitAt) ) .isInstanceOf(InvalidException.class) - .hasMessage("Must be in the future"); + .hasMessage("Must book in the future"); } ---- - -* - - - -
