From 2ff9d6dd0c1dbd6ca2f860efe126399c097f445f Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 22 Oct 2023 22:18:47 +0200 Subject: [PATCH] Add support for nested tests annotated with JUnit 5's @Nested (#11) Fixes #10 --- .../WireMockContextCustomizerFactory.java | 43 +++++++++- .../spring/WireMockSpringExtension.java | 36 ++++----- ...estedClassWireMockSpringExtensionTest.java | 80 +++++++++++++++++++ 3 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 wiremock-spring-boot/src/test/java/app/NestedClassWireMockSpringExtensionTest.java diff --git a/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockContextCustomizerFactory.java b/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockContextCustomizerFactory.java index e7520c2..b415b85 100644 --- a/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockContextCustomizerFactory.java +++ b/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockContextCustomizerFactory.java @@ -1,11 +1,14 @@ package com.maciejwalkowiak.wiremock.spring; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.TestContextAnnotationUtils; /** * Creates {@link WireMockContextCustomizer} for test classes annotated with {@link EnableWireMock}. @@ -16,12 +19,44 @@ public class WireMockContextCustomizerFactory implements ContextCustomizerFactor @Override public ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { - EnableWireMock annotation = AnnotationUtils.findAnnotation(testClass, EnableWireMock.class); + // scan class and all enclosing classes if the test class is @Nested + ConfigureWiremockHolder holder = new ConfigureWiremockHolder(); + parseDefinitions(testClass, holder); - if (annotation != null) { - return new WireMockContextCustomizer(annotation.value()); - } else { + if (holder.isEmpty()) { return null; + } else { + return new WireMockContextCustomizer(holder.asArray()); + } + } + + private void parseDefinitions(Class testClass, ConfigureWiremockHolder parser) { + parser.parse(testClass); + if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) { + parseDefinitions(testClass.getEnclosingClass(), parser); + } + } + + private static class ConfigureWiremockHolder { + private final List annotations = new ArrayList<>(); + + void add(ConfigureWireMock[] annotations) { + this.annotations.addAll(Arrays.asList(annotations)); + } + + void parse(Class clazz) { + EnableWireMock annotation = AnnotationUtils.findAnnotation(clazz, EnableWireMock.class); + if (annotation != null) { + add(annotation.value()); + } + } + + boolean isEmpty() { + return annotations.isEmpty(); + } + + ConfigureWireMock[] asArray() { + return annotations.toArray(new ConfigureWireMock[]{}); } } } diff --git a/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockSpringExtension.java b/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockSpringExtension.java index 9c63cd4..a4e0f97 100644 --- a/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockSpringExtension.java +++ b/wiremock-spring-boot/src/main/java/com/maciejwalkowiak/wiremock/spring/WireMockSpringExtension.java @@ -1,7 +1,9 @@ package com.maciejwalkowiak.wiremock.spring; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.List; +import java.util.function.Function; import com.github.tomakehurst.wiremock.WireMockServer; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -24,29 +26,21 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { Store.INSTANCE.findAllInstances(extensionContext).forEach(WireMockServer::resetAll); // inject properties into test class fields - injectWithLegacyWireMockAnnotation(extensionContext); - injectWireMockInstances(extensionContext); + injectWireMockInstances(extensionContext, WireMock.class, WireMock::value); + injectWireMockInstances(extensionContext, InjectWireMock.class, InjectWireMock::value); } - private static void injectWithLegacyWireMockAnnotation(ExtensionContext extensionContext) throws IllegalAccessException { - List annotatedFields = AnnotationSupport.findAnnotatedFields(extensionContext.getRequiredTestClass(), WireMock.class); - for (Field annotatedField : annotatedFields) { - WireMock annotation = annotatedField.getAnnotation(WireMock.class); - annotatedField.setAccessible(true); - - WireMockServer wiremock = Store.INSTANCE.findRequiredWireMockInstance(extensionContext, annotation.value()); - annotatedField.set(extensionContext.getRequiredTestInstance(), wiremock); - } - } - - private static void injectWireMockInstances(ExtensionContext extensionContext) throws IllegalAccessException { - List annotatedFields = AnnotationSupport.findAnnotatedFields(extensionContext.getRequiredTestClass(), InjectWireMock.class); - for (Field annotatedField : annotatedFields) { - InjectWireMock annotation = annotatedField.getAnnotation(InjectWireMock.class); - annotatedField.setAccessible(true); - - WireMockServer wiremock = Store.INSTANCE.findRequiredWireMockInstance(extensionContext, annotation.value()); - annotatedField.set(extensionContext.getRequiredTestInstance(), wiremock); + private static void injectWireMockInstances(ExtensionContext extensionContext, Class annotation, Function fn) throws IllegalAccessException { + // getRequiredTestInstances() return multiple instances for nested tests + for (Object testInstance : extensionContext.getRequiredTestInstances().getAllInstances()) { + List annotatedFields = AnnotationSupport.findAnnotatedFields(testInstance.getClass(), annotation); + for (Field annotatedField : annotatedFields) { + T annotationValue = annotatedField.getAnnotation(annotation); + annotatedField.setAccessible(true); + + WireMockServer wiremock = Store.INSTANCE.findRequiredWireMockInstance(extensionContext, fn.apply(annotationValue)); + annotatedField.set(testInstance, wiremock); + } } } diff --git a/wiremock-spring-boot/src/test/java/app/NestedClassWireMockSpringExtensionTest.java b/wiremock-spring-boot/src/test/java/app/NestedClassWireMockSpringExtensionTest.java new file mode 100644 index 0000000..64774c7 --- /dev/null +++ b/wiremock-spring-boot/src/test/java/app/NestedClassWireMockSpringExtensionTest.java @@ -0,0 +1,80 @@ +package app; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.maciejwalkowiak.wiremock.spring.ConfigureWireMock; +import com.maciejwalkowiak.wiremock.spring.EnableWireMock; +import com.maciejwalkowiak.wiremock.spring.InjectWireMock; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = NestedClassWireMockSpringExtensionTest.AppConfiguration.class) +@EnableWireMock({ + @ConfigureWireMock(name = "user-service", property = "user-service.url"), + @ConfigureWireMock(name = "todo-service", property = "todo-service.url"), + @ConfigureWireMock(name = "noproperty-service") +}) +public class NestedClassWireMockSpringExtensionTest { + + @SpringBootApplication + static class AppConfiguration { + } + + @Autowired + private Environment environment; + + @InjectWireMock("todo-service") + private WireMockServer topLevelClassTodoService; + + @Nested + @DisplayName("Test Something") + class NestedTest { + + @InjectWireMock("todo-service") + private WireMockServer nestedClassTodoService; + + @Test + void injectsWiremockServerToMethodParameter(@InjectWireMock("user-service") WireMockServer wireMockServer) { + assertWireMockServer(wireMockServer, "user-service.url"); + } + + @Test + void injectsWiremockServerToNestedClassField() { + assertWireMockServer(nestedClassTodoService, "todo-service.url"); + } + + @Test + void injectsWiremockServerToTopLevelClassField() { + assertWireMockServer(topLevelClassTodoService, "todo-service.url"); + } + + @Test + void doesNotSetPropertyWhenNotProvided(@InjectWireMock("noproperty-service") WireMockServer wireMockServer) { + assertThat(wireMockServer) + .as("creates WireMock instance") + .isNotNull(); + } + + private void assertWireMockServer(WireMockServer wireMockServer, String property) { + assertThat(wireMockServer) + .as("creates WireMock instance") + .isNotNull(); + assertThat(wireMockServer.baseUrl()) + .as("WireMock baseUrl is set") + .isNotNull(); + assertThat(wireMockServer.port()) + .as("sets random port") + .isNotZero(); + assertThat(environment.getProperty(property)) + .as("sets Spring property") + .isEqualTo(wireMockServer.baseUrl()); + } + } +}