Problem
When a test class uses @Nested inner classes to group tests, a @StartStop MockWebServer field declared on the outer class is never started. Any test inside a nested class that touches the server (e.g. mockWebServer.url(...)) fails with:
java.lang.IllegalStateException: call start() first
at mockwebserver3.MockWebServer.getSocketAddress(MockWebServer.kt:150)
at mockwebserver3.MockWebServer.getHostName(MockWebServer.kt:156)
at mockwebserver3.MockWebServer.url(MockWebServer.kt:200)
Minimal reproduction
class NestedStartStopTest {
@StartStop
private val mockWebServer = MockWebServer()
@Nested
inner class Group {
@Test
fun `server is running`() {
// fails: IllegalStateException: call start() first
mockWebServer.url("/")
}
}
}
Running the same assertion in a top-level @Test on the outer class works as expected.
Environment: com.squareup.okhttp3:mockwebserver3-junit5:5.3.2, JUnit Jupiter 5.x.
Root cause
In StartStopExtension.beforeEach:
override fun beforeEach(context: ExtensionContext) {
val testInstance = context.testInstance.get()
val instanceFields =
findAnnotatedFields(
context.requiredTestClass,
StartStop::class.java,
) { !Modifier.isStatic(it.modifiers) }
for (field in instanceFields) {
...
val server = field.get(testInstance) as? MockWebServer ?: continue
...
server.start()
}
}
For a nested test:
context.testInstance.get() returns the inner-class instance, not the outer one that holds the field.
context.requiredTestClass is the inner @Nested class, so findAnnotatedFields never sees fields declared on enclosing classes.
Result: the outer @StartStop field is invisible to the extension, server.start() is never invoked, and any nested test that uses the server hits IllegalStateException.
Suggested fix
Iterate context.requiredTestInstances.allInstances (which contains outer instances in order) and, for each, enumerate @StartStop-annotated fields on that instance's class:
override fun beforeEach(context: ExtensionContext) {
val store = context.getStore(Namespace.create(StartStop::class.java))
for (testInstance in context.requiredTestInstances.allInstances) {
val fields = findAnnotatedFields(
testInstance.javaClass,
StartStop::class.java,
) { !Modifier.isStatic(it.modifiers) }
for (field in fields) {
field.setAccessible(true)
val server = field.get(testInstance) as? MockWebServer ?: continue
store.put(field, server)
server.start()
}
}
}
This matches the pattern JUnit 5 recommends for extensions that need to work with @Nested.
Current workarounds
- Manual
@BeforeEach/@AfterEach on the outer class calling mockWebServer.start()/.close(). Outer-class @BeforeEach runs before each nested test.
@TestInstance(Lifecycle.PER_CLASS) + @BeforeAll/@AfterAll — server shared across all nested tests.
- Duplicate the
@StartStop field on every @Nested class.
- Drop
@Nested and flatten the test class.
Problem
When a test class uses
@Nestedinner classes to group tests, a@StartStop MockWebServerfield declared on the outer class is never started. Any test inside a nested class that touches the server (e.g.mockWebServer.url(...)) fails with:Minimal reproduction
Running the same assertion in a top-level
@Teston the outer class works as expected.Environment:
com.squareup.okhttp3:mockwebserver3-junit5:5.3.2, JUnit Jupiter 5.x.Root cause
In
StartStopExtension.beforeEach:For a nested test:
context.testInstance.get()returns the inner-class instance, not the outer one that holds the field.context.requiredTestClassis the inner@Nestedclass, sofindAnnotatedFieldsnever sees fields declared on enclosing classes.Result: the outer
@StartStopfield is invisible to the extension,server.start()is never invoked, and any nested test that uses the server hitsIllegalStateException.Suggested fix
Iterate
context.requiredTestInstances.allInstances(which contains outer instances in order) and, for each, enumerate@StartStop-annotated fields on that instance's class:This matches the pattern JUnit 5 recommends for extensions that need to work with
@Nested.Current workarounds
@BeforeEach/@AfterEachon the outer class callingmockWebServer.start()/.close(). Outer-class@BeforeEachruns before each nested test.@TestInstance(Lifecycle.PER_CLASS)+@BeforeAll/@AfterAll— server shared across all nested tests.@StartStopfield on every@Nestedclass.@Nestedand flatten the test class.