Make JUnit Jupiter extension stateless so it can be used in parallel

Previously, REST Docs' JUnit Jupiter extension,
RestDocumentationExtension, stored its own state. Since the code
was written I have learned that this isn't recommended and that the
extension context's store should be used instead. Up until now, we'd
got away with the extension being stateful, but the introduction of
parallel test execution has highlighted the problem.

This commit updates RestDocumentationExtension to use the store from
the execution context rather than storing its own state. An unwanted,
but necessary, side-effect of this is that RestDocumentationExtension
itself no longer implements RestDocumentationContextProvider and,
instead, returns a new type rather than itself when asked to resolve
the provider. This is technically a breaking API change, but users
will be unaffected if they have followed the recommended and
documented approach of injecting RestDocumentationContextProvider.

Closes gh-520
This commit is contained in:
Andy Wilkinson
2018-06-26 10:21:30 +01:00
parent b99f494ed8
commit 99ee889708
2 changed files with 20 additions and 22 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@@ -42,7 +43,7 @@ public class SampleJUnit5ApplicationTests {
private MockMvc mockMvc;
@BeforeEach
public void setUp(RestDocumentationExtension restDocumentation) {
public void setUp(RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(restDocumentation)).build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,11 @@
package org.springframework.restdocs;
import java.lang.reflect.Method;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
@@ -31,28 +30,18 @@ import org.junit.jupiter.api.extension.ParameterResolver;
*
* @author Andy Wilkinson
*/
public class RestDocumentationExtension implements Extension, BeforeEachCallback,
AfterEachCallback, RestDocumentationContextProvider, ParameterResolver {
private final ManualRestDocumentation delegate = new ManualRestDocumentation();
public class RestDocumentationExtension
implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Class<?> testClass = context.getTestClass().orElseThrow(
() -> new IllegalStateException("No test class was available"));
Method testMethod = context.getTestMethod().orElseThrow(
() -> new IllegalStateException("No test method was available"));
this.delegate.beforeTest(testClass, testMethod.getName());
this.getDelegate(context).beforeTest(context.getRequiredTestClass(),
context.getRequiredTestMethod().getName());
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
this.delegate.afterTest();
}
@Override
public RestDocumentationContext beforeOperation() {
return this.delegate.beforeOperation();
this.getDelegate(context).afterTest();
}
@Override
@@ -64,8 +53,16 @@ public class RestDocumentationExtension implements Extension, BeforeEachCallback
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return this;
ExtensionContext context) {
return (RestDocumentationContextProvider) () -> getDelegate(context)
.beforeOperation();
}
private ManualRestDocumentation getDelegate(ExtensionContext context) {
Namespace namespace = Namespace.create(getClass(), context.getUniqueId());
return context.getStore(namespace).getOrComputeIfAbsent(
ManualRestDocumentation.class, (key) -> new ManualRestDocumentation(),
ManualRestDocumentation.class);
}
}