Skip to content

Commit

Permalink
Add support for nested tests annotated with JUnit 5's @nested
Browse files Browse the repository at this point in the history
Fixes #10
  • Loading branch information
maciejwalkowiak committed Oct 22, 2023
1 parent b10ab31 commit 65f9b2f
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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}.
Expand All @@ -16,12 +19,44 @@ public class WireMockContextCustomizerFactory implements ContextCustomizerFactor
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> 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<ConfigureWireMock> 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[]{});
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Field> 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<Field> 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 <T extends Annotation> void injectWireMockInstances(ExtensionContext extensionContext, Class<T> annotation, Function<T, String> fn) throws IllegalAccessException {
// getRequiredTestInstances() return multiple instances for nested tests
for (Object testInstance : extensionContext.getRequiredTestInstances().getAllInstances()) {
List<Field> 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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
}

0 comments on commit 65f9b2f

Please sign in to comment.