Skip to content

Commit ac276e2

Browse files
author
Ronald Holshausen
committedNov 9, 2019
feat: allow JUnit 5 tests to have state change methods on additional classes #943
1 parent fb4530c commit ac276e2

File tree

7 files changed

+135
-19
lines changed

7 files changed

+135
-19
lines changed
 

‎provider/pact-jvm-provider-junit/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ be expanded in the expression. For this to work, just make your provider state m
157157

158158
### Using multiple classes for the state change methods
159159

160-
If you have a large number of state change methods, you can split things up bu moving them to other classes. There are
160+
If you have a large number of state change methods, you can split things up by moving them to other classes. There are
161161
two ways you can do this:
162162

163163
#### Use interfaces

‎provider/pact-jvm-provider-junit5/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ For example, configure it by adding the following to your POM:
6565

6666
Provider State Methods work in the same way as with JUnit 4 tests, refer to the [Pact junit runner](../pact-jvm-provider-junit/README.md) docs.
6767

68+
### Using multiple classes for the state change methods
69+
70+
If you have a large number of state change methods, you can split things up by moving them to other classes. You will
71+
need to specify the additional classes on the test context in a `Before` method. Do this with the `withStateHandler`
72+
or `setStateHandlers` methods. See [StateAnnotationsOnAdditionalClassTest](pact-jvm-provider-junit5/src/test/java/au/com/dius/pact/provider/junit5/StateAnnotationsOnAdditionalClassTest.java) for an example.
73+
6874
## Modifying the requests before they are sent
6975

7076
**Important Note:** You should only use this feature for things that can not be persisted in the pact file. By modifying

‎provider/pact-jvm-provider-junit5/src/main/kotlin/au/com/dius/pact/provider/junit5/PactJUnit5VerificationProvider.kt

+38-11
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import kotlin.reflect.full.findAnnotation
4949
* The instance that holds the context for the test of an interaction. The test target will need to be set on it in
5050
* the before each phase of the test, and the verifyInteraction method must be called in the test template method.
5151
*/
52-
data class PactVerificationContext(
52+
data class PactVerificationContext @JvmOverloads constructor(
5353
private val store: ExtensionContext.Store,
5454
private val context: ExtensionContext,
5555
var target: TestTarget = HttpTestTarget(port = 8080),
@@ -60,6 +60,7 @@ data class PactVerificationContext(
6060
val interaction: Interaction,
6161
internal var testExecutionResult: TestResult = TestResult.Ok
6262
) {
63+
val stateChangeHandlers: MutableList<Any> = mutableListOf()
6364
var executionContext: Map<String, Any>? = null
6465

6566
/**
@@ -109,6 +110,15 @@ data class PactVerificationContext(
109110
interaction.description, failures)
110111
}
111112
}
113+
114+
fun withStateChangeHandlers(vararg stateClasses: Any): PactVerificationContext {
115+
stateChangeHandlers.addAll(stateClasses)
116+
return this
117+
}
118+
119+
fun addStateChangeHandlers(vararg stateClasses: Any) {
120+
stateChangeHandlers.addAll(stateClasses)
121+
}
112122
}
113123

114124
/**
@@ -265,7 +275,8 @@ class PactVerificationStateChangeExtension(
265275
val testContext = store.get("interactionContext") as PactVerificationContext
266276

267277
try {
268-
val providerStateContext = invokeStateChangeMethods(extensionContext, interaction.providerStates, StateChangeAction.SETUP)
278+
val providerStateContext = invokeStateChangeMethods(extensionContext, testContext,
279+
interaction.providerStates, StateChangeAction.SETUP)
269280
testContext.executionContext = mapOf("providerState" to providerStateContext)
270281
} catch (e: Exception) {
271282
logger.error(e) { "Provider state change callback failed" }
@@ -276,28 +287,33 @@ class PactVerificationStateChangeExtension(
276287

277288
override fun afterTestExecution(context: ExtensionContext) {
278289
logger.debug { "afterEach for interaction '${interaction.description}'" }
279-
invokeStateChangeMethods(context, interaction.providerStates, StateChangeAction.TEARDOWN)
290+
val store = context.getStore(ExtensionContext.Namespace.create("pact-jvm"))
291+
val testContext = store.get("interactionContext") as PactVerificationContext
292+
293+
invokeStateChangeMethods(context, testContext, interaction.providerStates, StateChangeAction.TEARDOWN)
280294
}
281295

282296
private fun invokeStateChangeMethods(
283297
context: ExtensionContext,
298+
testContext: PactVerificationContext,
284299
providerStates: List<ProviderState>,
285300
action: StateChangeAction
286301
): Map<String, Any?> {
287302
val errors = mutableListOf<String>()
288303

289304
val providerStateContext = mutableMapOf<String, Any?>()
290305
providerStates.forEach { state ->
291-
val stateChangeMethods = findStateChangeMethods(context.requiredTestClass, state)
306+
val stateChangeMethods = findStateChangeMethods(context.requiredTestInstance,
307+
testContext.stateChangeHandlers, state)
292308
if (stateChangeMethods.isEmpty()) {
293309
errors.add("Did not find a test class method annotated with @State(\"${state.name}\")")
294310
} else {
295-
stateChangeMethods.filter { it.second.action == action }.forEach { (method, _) ->
296-
logger.debug { "Invoking state change method ${method.name} for state '${state.name}'" }
311+
stateChangeMethods.filter { it.second.action == action }.forEach { (method, _, instance) ->
312+
logger.debug { "Invoking state change method ${method.name} for state '${state.name}' on $instance" }
297313
val stateChangeValue = if (method.parameterCount > 0) {
298-
ReflectionSupport.invokeMethod(method, context.requiredTestInstance, state.params)
314+
ReflectionSupport.invokeMethod(method, instance, state.params)
299315
} else {
300-
ReflectionSupport.invokeMethod(method, context.requiredTestInstance)
316+
ReflectionSupport.invokeMethod(method, instance)
301317
}
302318

303319
if (stateChangeValue is Map<*, *>) {
@@ -314,9 +330,20 @@ class PactVerificationStateChangeExtension(
314330
return providerStateContext
315331
}
316332

317-
private fun findStateChangeMethods(testClass: Class<*>, state: ProviderState): List<Pair<Method, State>> {
318-
return AnnotationSupport.findAnnotatedMethods(testClass, State::class.java, HierarchyTraversalMode.TOP_DOWN)
319-
.map { it to it.getAnnotation(State::class.java) }
333+
private fun findStateChangeMethods(
334+
testClass: Any,
335+
stateChangeHandlers: List<Any>,
336+
state: ProviderState
337+
): List<Triple<Method, State, Any>> {
338+
val stateChangeClasses =
339+
AnnotationSupport.findAnnotatedMethods(testClass.javaClass, State::class.java, HierarchyTraversalMode.TOP_DOWN)
340+
.map { it to testClass }
341+
.plus(stateChangeHandlers.flatMap { handler ->
342+
AnnotationSupport.findAnnotatedMethods(handler.javaClass, State::class.java, HierarchyTraversalMode.TOP_DOWN)
343+
.map { it to handler }
344+
})
345+
return stateChangeClasses
346+
.map { Triple(it.first, it.first.getAnnotation(State::class.java), it.second) }
320347
.filter { it.second.value.any { s -> state.name == s } }
321348
}
322349

‎provider/pact-jvm-provider-junit5/src/test/groovy/au/com/dius/pact/provider/junit5/PactVerificationStateChangeExtensionSpec.groovy

+14-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class PactVerificationStateChangeExtensionSpec extends Specification {
2424
Interaction interaction
2525
private TestResultAccumulator testResultAcc
2626
RequestResponsePact pact
27+
private PactVerificationContext pactContext
28+
private ExtensionContext testContext
29+
private ExtensionContext.Store store
2730

2831
static class TestClass {
2932

@@ -53,11 +56,20 @@ class PactVerificationStateChangeExtensionSpec extends Specification {
5356
}
5457
}
5558

59+
private TestClass testInstance
60+
5661
def setup() {
5762
interaction = new RequestResponseInteraction('test')
5863
pact = new RequestResponsePact(new Provider(), new Consumer(), [ interaction ])
5964
testResultAcc = Mock(TestResultAccumulator)
6065
verificationExtension = new PactVerificationStateChangeExtension(pact, interaction, testResultAcc)
66+
testInstance = new TestClass()
67+
testContext = [
68+
'getTestClass': { Optional.of(TestClass) },
69+
'getTestInstance': { Optional.of(testInstance) }
70+
] as ExtensionContext
71+
store = [:] as ExtensionContext.Store
72+
pactContext = new PactVerificationContext(store, testContext, 'test', interaction)
6173
}
6274

6375
@Unroll
@@ -66,8 +78,7 @@ class PactVerificationStateChangeExtensionSpec extends Specification {
6678
def state = new ProviderState('test state')
6779

6880
when:
69-
verificationExtension.invokeStateChangeMethods(['getTestClass': { Optional.of(testClass) } ] as ExtensionContext,
70-
[state], StateChangeAction.SETUP)
81+
verificationExtension.invokeStateChangeMethods(testContext, pactContext, [state], StateChangeAction.SETUP)
7182

7283
then:
7384
thrown(MissingStateChangeMethod)
@@ -80,16 +91,12 @@ class PactVerificationStateChangeExtensionSpec extends Specification {
8091
def 'invokes the state change method for the provider state'() {
8192
given:
8293
def state = new ProviderState('Test 2', [a: 'A', b: 'B'])
83-
def testInstance = new TestClass()
8494

8595
when:
8696
testInstance.state2Called = false
8797
testInstance.state2TeardownCalled = false
8898
testInstance.state3Called = null
89-
verificationExtension.invokeStateChangeMethods([
90-
'getTestClass': { Optional.of(TestClass) },
91-
'getTestInstance': { Optional.of(testInstance) }
92-
] as ExtensionContext, [state], StateChangeAction.SETUP)
99+
verificationExtension.invokeStateChangeMethods(testContext, pactContext, [state], StateChangeAction.SETUP)
93100

94101
then:
95102
testInstance.state2Called
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package au.com.dius.pact.provider.junit5;
2+
3+
import au.com.dius.pact.provider.junit.Provider;
4+
import au.com.dius.pact.provider.junit.loader.PactFolder;
5+
import com.github.tomakehurst.wiremock.WireMockServer;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.TestTemplate;
8+
import org.junit.jupiter.api.extension.ExtendWith;
9+
import ru.lanwen.wiremock.ext.WiremockResolver;
10+
import ru.lanwen.wiremock.ext.WiremockResolver.Wiremock;
11+
import ru.lanwen.wiremock.ext.WiremockUriResolver;
12+
import ru.lanwen.wiremock.ext.WiremockUriResolver.WiremockUri;
13+
14+
import java.net.MalformedURLException;
15+
import java.net.URL;
16+
17+
@Provider("providerWithMultipleInteractions")
18+
@PactFolder("pacts")
19+
@ExtendWith({
20+
WiremockResolver.class,
21+
WiremockUriResolver.class
22+
})
23+
class StateAnnotationsOnAdditionalClassTest {
24+
25+
private WireMockServer server;
26+
27+
@TestTemplate
28+
@ExtendWith(PactVerificationInvocationContextProvider.class)
29+
void testTemplate(PactVerificationContext context) {
30+
context.verifyInteraction();
31+
}
32+
33+
@BeforeEach
34+
void before(PactVerificationContext context, @Wiremock WireMockServer server,
35+
@WiremockUri String uri) throws MalformedURLException {
36+
this.server = server;
37+
context.addStateChangeHandlers(new StateClass1(server), new StateClass2(server));
38+
context.setTarget(HttpTestTarget.fromUrl(new URL(uri)));
39+
}
40+
41+
public WireMockServer server() {
42+
return this.server;
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package au.com.dius.pact.provider.junit5;
2+
3+
import com.github.tomakehurst.wiremock.WireMockServer;
4+
5+
public class StateClass1 implements StateInterface1 {
6+
private final WireMockServer server;
7+
8+
public StateClass1(WireMockServer server) {
9+
this.server = server;
10+
}
11+
12+
@Override
13+
public WireMockServer server() {
14+
return server;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package au.com.dius.pact.provider.junit5;
2+
3+
import com.github.tomakehurst.wiremock.WireMockServer;
4+
5+
public class StateClass2 implements StateInterface2 {
6+
private final WireMockServer server;
7+
8+
public StateClass2(WireMockServer server) {
9+
this.server = server;
10+
}
11+
12+
@Override
13+
public WireMockServer server() {
14+
return server;
15+
}
16+
}

0 commit comments

Comments
 (0)
Please sign in to comment.