# Common Tools used for testing Java Web Services A short introduction into common tools that I use in all of my projects for testing Java web applications and services. This presentation covers the essential testing tools in the Java ecosystem that enable effective unit, integration, and acceptance testing. We'll explore libraries and frameworks that support different testing strategies including mocking, assertion handling, and container-based testing. --- ## Agenda - Types of testing - Value-based - State-based - Interaction-based - Test objects - Mocks - Spies - Stubs - Tools and frameworks - JUnit5 Jupyter - Mockito - AssertJ - TestContainers - LocalStack - WireMock - Gradle - Maven - Questions - Resources --- ## Value-based testing * Tests focus on verifying that methods return the correct values * Also known as output-based testing * Pure functions with no side effects are ideal for this approach * Assertions verify the returned value matches expected results ```java @Test void shouldCalculateCorrectSum() { // arrange Calculator calculator = new Calculator(); // act int result = calculator.add(2, 3); // assert assertEquals(5, result); } ``` --- ## State-based testing * Tests verify the state of the system after an operation * Focuses on checking object properties or system state changes * Useful when methods modify object state or have side effects * Often uses argument captors to verify state changes ```java @Test void shouldAddItemToShoppingCart() { // arrange ShoppingCart cart = new ShoppingCart(); Item item = new Item("Book", 20.0); // act cart.addItem(item); // assert assertEquals(1, cart.getItemCount()); assertTrue(cart.contains(item)); assertEquals(20.0, cart.getTotalValue()); } ``` --- ## Interaction-based testing * Tests verify that methods are called correctly on dependencies * Also known as behaviour verification or communication-based testing * Uses mocks to verify method calls, call counts, and parameters * Focuses on how objects collaborate rather than the final result ```java @Test void shouldCallEmailServiceWhenOrderCompleted() { // arrange EmailService emailService = mock(EmailService.class); OrderProcessor processor = new OrderProcessor(emailService); Order order = new Order("123", "customer@example.com"); // act processor.completeOrder(order); // assert verify(emailService).sendConfirmation("customer@example.com", "123"); verify(emailService, times(1)).sendConfirmation(anyString(), anyString()); } ``` --- ## Test objects (doubles .red[*]) Test doubles are objects that are used in tests to replace real objects, to model behaviour and state, to arrange the setup for testing. ### Types of test doubles: - Mocks - Spies - Stubs .footnote[.red[*] See Martin Fowler on [Doubles](https://martinfowler.com/bliki/TestDouble.html)] --- ### Mocks .red[*] * Mocks are test objects that are used for *behaviour testing* * Replace dependencies in tests and are used to assert for * Method calls (a certain method was called, or not) * Methods are called with expected values (using argument captors, or argument matchers) * Should throw an exception when they receive a call they don't expect * Are part of the assertion category .footnote[.red[*] See Martin Fowler on [Mocks](https://martinfowler.com/articles/mocksArentStubs.html)] --- ### Spies * Spies wrap real objects and observe their behaviour * Allow calling real methods while recording interactions * Useful when you want to test partial behaviour of real objects * Can verify method calls on real objects without replacing them entirely ```java @Test void shouldSpyOnRealObject() { // arrange List realList = new ArrayList<>(); List spyList = spy(realList); // act spyList.add("item1"); spyList.add("item2"); // assert verify(spyList, times(2)).add(anyString()); assertEquals(2, spyList.size()); // Real behaviour preserved assertTrue(spyList.contains("item1")); } ``` --- ### Stubs * Stubs provide predetermined responses to method calls * Used to isolate the system under test from dependencies * Return specific values or throw exceptions as needed for test scenarios * Do not verify interactions - only provide necessary data ```java @Test void shouldUseStubForExternalService() { // arrange PaymentService paymentStub = mock(PaymentService.class); when(paymentStub.processPayment(100.0)).thenReturn(true); when(paymentStub.processPayment(0.0)).thenThrow(new IllegalArgumentException()); OrderService orderService = new OrderService(paymentStub); // act boolean result = orderService.processOrder(100.0); // assert assertTrue(result); // No verification of method calls - stubs just provide data } ``` --- ## JUnit 5 - Jupyter .red[*] * JUnit5 Jupyter is a component of the JUnit5 framework * It provides a test engine for running tests on the JVM * Provides a programming model and an extension model for writing tests .footnote[.red[*] See JUnit5 [Documentation](https://junit.org/junit5/docs/current/user-guide/)] --- ### JUnit5 - General Notes * A test class doesn't need to be marked as `public` * Test classes don't need to be marked with any annotations * Test methods don't need to be `public` * Test classes can be extended with `@ExtendWith()` annotation * Provides an assertion package ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class SampleTest { @Test void shouldBeTrue() { assertEquals(2, 2); } } ``` --- ### JUnit5 - Popular APIs * `@Test` - used to mark tests such that JUnit will run them * `@ParameterizedTest` - run same tests multiple times with different arguments * `@DisplayName` - custom test name, overrides the default name for a method. Useful in Java to name tests with spaces etc. * `@BeforeEach` - used to decorate a method that will run before each test * `BeforeAll` - used to decorate a method that will run before all tests start running. Similar to `@BeforeAll` from JUnit4 * `@AfterEach` - decorate a method that will run after each test * `AfterAll` - decorate a method that will run after all tests * `@Tag` - declares tags for filtering tests at class or method level * `@Disabled` - disable a test. Will be ignored by the test engine --- ### JUnit5 - Parameterized Tests Parameterized tests are a useful way to run same test multiple times with multiple parameters. Instead of duplicating test cases for variation between parameters, it can re-use the same test and provide variations between parameters. ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class SampleTest { @ParameterizedTest(name = "When input is {0} then the result should be {1}") @CsvSource({"2,2","4,4"}) void shouldBeTrue(int testVal, int expected) { assertEquals(testVal, expected); } } ``` --- ## Mockito .red[*] Mockito is an open source testing framework for Java, that allows creation of mocks, stubs and spyes. It can create mocks in several ways: * Using a JUnit extension and annotations * Creating mocks using static factory methods .footnote[.red[*] See Mockito's [Documentation](https://site.mockito.org/)] --- ### Using Mockito's JUnit extension ```java @ExtendWith(MockitoExtension.class) class SampleTest { @InjectMocks private HotelCreator sut; @Mock private HotelRepository repository; @Test void shouldCreateHotel() { // arrange var hotel = Hotel.builder().build(); var hotelEntity = HotelEntity.builder().build(); when(repository.create(hotelEntity)).thenReturn(anotherEntity); // act sut.create(hotel); // assert verify(repository).create(hotelEntity); // more assertions here } } ``` --- ### Using Mockito's static factory methods ```java class SampleTest { private HotelCreator sut; private final HotelRepository repository = Mockito.mock(HotelRepository.class); @BeforeEach void setup() { Mockito.reset(repository); sut = new HotelCreator(repository); } @Test void shouldCreateHotel() { // arrange var hotel = Hotel.builder().build(); var hotelEntity = HotelEntity.builder().build(); when(repository.create(hotelEntity)).thenReturn(anotherEntity); // act sut.create(hotel); // assert verify(repository).create(hotelEntity); // more assertions here } } ``` --- ### Using Mockito's Argument Captor * `ArgumentCaptor` is used to capture method arguments when are called in tests * Useful to further assert on these objects * Used for state based testing ```java @Test void shouldCaptureAndVerify() { // arrange var expectedEntity = EntityCreator.createDefault(); var hotel = Hotel.builder().build(); ArgumentCaptor hotelCaptor = ArgumentCaptor.forClass(HotelEntity.class); when(repository.save(hotelCaptor.capture())).thenReturn(anotherObject); // act repository.save(hotel); // assert var actualEntity = hotelCaptor.getValue(); assertThat(actualEntity).isNotNull().isEqualTo(expectedEntity); } ``` --- ## TestContainers .red[*] Testcontainers is a Java library that provides automatic lifecycle management to third party dependencies running in Docker containers and that are used in testing. Most common usecases are databases, AWS stack or messaging tools. ### Advantages * Allows using in tests same version for dependencies as used in production * The container(s) lifecycle management is handled by the library * Containers start and stop automatically, when needed * Configurable via modules * Can specify which version from your dependency to run * Integrates seamlessly with testing frameworks * Requires an environment capable of running Docker containers .footnote[.red[*] See TestContainers' [Documentation](https://java.testcontainers.org/)] --- ## AssertJ .red[*] AssertJ is an assertions library, providing a rich set of assertions, designed for readability and fluent code. ### Advantages * Allows chaining assertion methods to have a fluent code * Provides assertions for collections, exceptions, object fields, comparing field by field etc. * Can be configured * Provides an extension mechanism .footnote[.red[*] See AssertJ's [Documentation](https://assertj.github.io/doc/)] --- ### Fluent Assertions with AssertJ #### Simple assertions ```java assertThat("The Lord of the Rings") .isNotNull() .startsWith("The") .contains("Lord") .endsWith("Rings"); ``` #### Field by field recursive assertion ```java assertThat(sherlock).usingRecursiveComparison() .ignoringFields("name", "home.address.street") .isEqualTo(moriarty); ``` #### Exception assertion ```java assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> sut.executeAndThrow()) .havingRootCause() .withMessage("root error"); ``` --- ## LocalStack .red[*] LocalStack is a cloud service emulator that runs in a single container on your machine for testing and developing cloud and serverless applications offline. ### Use Cases * Test AWS integrations locally without connecting to real AWS services * Develop and test Lambda functions, S3 buckets, DynamoDB tables, SQS queues * Integration testing for cloud-native applications * Cost-effective testing without AWS charges .footnote[.red[*] See LocalStack's [Documentation](https://localstack.cloud/)] --- ### Advantages of using LocalStack * Supports most AWS services (S3, Lambda, DynamoDB, SQS, SNS, etc.) * Runs locally in Docker containers * No internet connection required for testing * Integrates with TestContainers for automated test lifecycle * Faster feedback loop compared to real AWS testing --- ### Example of using LocalStack with TestContainers ```java @Testcontainers class S3ServiceTest { @Container static LocalStackContainer localstack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:latest")) .withServices(LocalStackContainer.Service.S3); @Test void shouldUploadFileToS3() { // arrange S3Client s3Client = S3Client.builder() .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3)) .credentialsProvider(StaticCredentialsProvider.create( AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey()))) .region(Region.of(localstack.getRegion())) .build(); s3Client.createBucket(CreateBucketRequest.builder().bucket("test-bucket").build()); // act s3Client.putObject(PutObjectRequest.builder() .bucket("test-bucket") .key("test-file.txt") .build(), RequestBody.fromString("Hello LocalStack!")); // assert GetObjectResponse response = s3Client.getObject(GetObjectRequest.builder() .bucket("test-bucket") .key("test-file.txt") .build(), ResponseTransformer.toBytes()); assertThat(response.contentLength()).isGreaterThan(0); } } ``` --- ## WireMock .red[*] WireMock is a flexible API mocking tool for fast, robust and comprehensive testing. It can mock HTTP services by running as a standalone server or embedded within your tests. ### Use Cases * Mock external REST APIs and web services during testing * Create test doubles for microservices integration testing * Simulate network failures and slow responses * Test API contracts and service interactions ### Advantages * Standalone server or embedded mode * Record and playback real API interactions * Advanced request matching (URL, headers, body) * Response templating and transformation * Fault injection and latency simulation .footnote[.red[*] See WireMock's [Documentation](https://wiremock.org/)] --- ### Example of using WireMock ```java @Test void shouldCallExternalApiWithWireMock() { // arrange WireMockServer wireMockServer = new WireMockServer(8089); wireMockServer.start(); wireMockServer.stubFor(get(urlEqualTo("/api/users/123")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody("{\"id\":123,\"name\":\"John Doe\"}"))); UserService userService = new UserService("http://localhost:8089"); // act User user = userService.getUser(123); // assert assertThat(user.getId()).isEqualTo(123); assertThat(user.getName()).isEqualTo("John Doe"); wireMockServer.stop(); } ``` --- ## Build Tools for Testing Both Gradle and Maven provide comprehensive testing capabilities with different approaches to organizing and running unit and integration tests. --- ## Gradle Testing .red[*] Gradle provides built-in testing capabilities through the Java plugin with flexible test task configuration. ### Unit Tests * Uses `test` task by default * Runs tests from `src/test/java` * Automatic dependency management for test frameworks ### Integration Tests * Custom test tasks (e.g., `integrationTest`) * Separate source sets: `src/integrationTest/java` * Can run against running applications or use TestContainers ```gradle tasks.register('integrationTest', Test) { useJUnitPlatform() testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath } ``` .footnote[.red[*] See Gradle [Testing Documentation](https://docs.gradle.org/current/userguide/java_testing.html)] --- ## Maven Testing Plugins .red[*] Maven uses dedicated plugins for different test phases with clear separation between unit and integration tests. ### Surefire Plugin (Unit Tests) * Runs during `test` phase * Tests from `src/test/java` * Fails fast on test failures ### Failsafe Plugin (Integration Tests) * Runs during `integration-test` phase * Tests from `src/test/java` with `*IT.java` naming convention * Defers failure until `post-integration-test` phase .footnote[.red[*] See Maven [Surefire](https://maven.apache.org/surefire/maven-surefire-plugin/) and [Failsafe](https://maven.apache.org/surefire/maven-failsafe-plugin/) Documentation] --- ### Maven FailSafe Plugin - Configuration Example ```xml org.apache.maven.plugins maven-failsafe-plugin integration-test verify ``` --- ## Resources ### Official Documentation #### Testing Frameworks & Libraries * [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/) * [Mockito Javadoc Documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html) * [TestContainers Documentation](https://java.testcontainers.org/) * [AssertJ Documentation](https://assertj.github.io/doc/) * [LocalStack - Local AWS Cloud Stack](https://localstack.cloud/) * [WireMock - API Mocking Framework](https://wiremock.org/) #### Build Tools & Plugins * [Gradle Testing in Java & JVM projects](https://docs.gradle.org/current/userguide/java_testing.html) * [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin/) * [Maven Failsafe Plugin](https://maven.apache.org/surefire/maven-failsafe-plugin/) --- ## Resources - Continued ### Articles and Guides * [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) * [Test Double](https://martinfowler.com/bliki/TestDouble.html) * [The Clean Code Blog - Test Definitions](https://blog.cleancoder.com/uncle-bob/2017/05/05/TestDefinitions.html) ### Books * [Unit Testing Principles, Practices, and Patterns](#) by Vladimir Khorikov * [Growing Object-Oriented Software, Guided by Tests](#) by Steve Freeman & Nat Pryce * [Test-Driven Development: By Example](#) by Kent Beck --- ## Questions? --- ## Thank you! </pre>