Verification and request journal
Mokksy provides two complementary verification methods that check opposite sides of the stub/request contract.
Verify all stubs were triggered
verifyNoUnmatchedStubs() fails if any registered stub was never matched by an incoming request.
Use this to catch stubs you set up but that were never actually called — a sign the code under test took
a different path than expected.
1// Fails if any stub has never been matched
2mokksy.verifyNoUnmatchedStubs()1// Fails if any stub has never been matched
2mokksy.verifyNoUnmatchedStubs();Note: Be careful when running tests in parallel against a single
MokksyServerinstance. Some stubs might be unmatched when one test completes. Avoid calling this in@AfterEach/@AfterTestunless each test owns its own server instance.
Verify no unexpected requests arrived
verifyNoUnexpectedRequests() fails if any HTTP request arrived at the server but no stub matched it.
These requests are recorded in the RequestJournal and reported together.
1// Fails if any request arrived with no matching stub
2mokksy.verifyNoUnexpectedRequests()1// Fails if any request arrived with no matching stub
2mokksy.verifyNoUnexpectedRequests();Recommended AfterEach setup
Always run verifyNoUnexpectedRequests() in @AfterEach to catch requests that arrived but
matched no stub. For verifyNoUnmatchedStubs(), the right placement depends on your fixture strategy:
- Per-test instance (
@TestInstance(Lifecycle.PER_METHOD)or a fresh server per test): call both checks in@AfterEach— every stub registered during that test should have been matched before the server is torn down. - Shared instance (
@TestInstance(Lifecycle.PER_CLASS)or a companion-object server): callverifyNoUnmatchedStubs()in@AfterAll, immediately beforeshutdown(). Calling it after each individual test would falsely report stubs registered for later tests as unmatched.
1@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2class MyTest {
3
4 val mokksy = Mokksy(verbose = true)
5 lateinit var client: HttpClient
6
7 @BeforeAll
8 suspend fun setup() {
9 mokksy.startSuspend()
10 mokksy.awaitStarted() // port() and baseUrl() are safe after this point
11 client = HttpClient {
12 install(DefaultRequest) {
13 url(mokksy.baseUrl())
14 }
15 }
16 }
17
18 @Test
19 suspend fun testSomething() {
20 mokksy.get {
21 path("/hi")
22 } respondsWith {
23 delay = 100.milliseconds // wait 100ms, then reply
24 body = "Hello"
25 }
26
27 // when
28 val response = client.get("/hi")
29
30 // then
31 response.status shouldBe HttpStatusCode.OK
32 response.bodyAsText() shouldBe "Hello"
33 }
34
35 @AfterEach
36 fun afterEach() {
37 mokksy.verifyNoUnexpectedRequests()
38 }
39
40 @AfterAll
41 suspend fun afterAll() {
42 client.close()
43 mokksy.verifyNoUnmatchedStubs() // shared instance: check once, after all tests ran
44 mokksy.shutdownSuspend()
45 }
46} 1@TestInstance(TestInstance.Lifecycle.PER_CLASS)
2class MyTest {
3
4 private final Mokksy mokksy = Mokksy.create().start();
5 private final HttpClient httpClient = HttpClient.newHttpClient();
6
7 @Test
8 void testSomething() throws Exception {
9 mokksy.get(spec -> spec.path("/hi"))
10 .respondsWith(response -> response
11 .delayMillis(100)
12 .body("Hello"));
13
14 var response = httpClient.send(
15 HttpRequest.newBuilder()
16 .uri(URI.create(mokksy.baseUrl() + "/hi"))
17 .GET()
18 .build(),
19 HttpResponse.BodyHandlers.ofString()
20 );
21
22 assertThat(response.statusCode()).isEqualTo(200);
23 assertThat(response.body()).isEqualTo("Hello");
24 }
25
26 @AfterEach
27 void afterEach() {
28 mokksy.verifyNoUnexpectedRequests();
29 }
30
31 @AfterAll
32 void afterAll() {
33 mokksy.verifyNoUnmatchedStubs();
34 mokksy.shutdown();
35 }
36}Inspecting unmatched items
Use the find* variants to retrieve the unmatched items directly for custom assertions:
1// List<RecordedRequest> — HTTP requests with no matching stub
2val unmatchedRequests: List<RecordedRequest> = mokksy.findAllUnexpectedRequests()
3
4// List<RecordedRequest> — matched requests, populated only in JournalMode.FULL
5val matchedRequests: List<RecordedRequest> = mokksy.findAllMatchedRequests()
6
7// List<StubHandle> — stubs that were never triggered
8val unmatchedStubs: List<StubHandle> = mokksy.findAllUnmatchedStubs()1// List<RecordedRequest> - HTTP requests with no matching stub
2var unmatchedRequests = mokksy.findAllUnexpectedRequests();
3
4// List<StubHandle> - stubs that were never triggered
5var unmatchedStubs = mokksy.findAllUnmatchedStubs();StubHandle and RecordedRequest answer different questions:
StubHandleidentifies a registered stub. It exposes the optional stubname,matchCount(), and verification helpers such asverifyCalled(). It does not contain HTTP request details.RecordedRequestis the request journal entry. It captures the incoming requestmethod,uri,headers, whether itmatcheda stub, andbodyAsTextwhen Mokksy can safely read the request body as text.
Use StubHandle when you need to verify that a known stub was called. Use RecordedRequest when
you need to inspect what the client actually sent, especially for unexpected requests.
Request journal
Mokksy records incoming requests in a RequestJournal. The recording mode is controlled by JournalMode in
ServerConfiguration:
- JournalMode.NONE - Disables request recording entirely.
findAllUnexpectedRequests(),findAllMatchedRequests(), andverifyNoUnexpectedRequests()throwIllegalStateException. - JournalMode.LEAN (default) – Records only requests with no matching stub. Lower overhead; sufficient for
verifyNoUnexpectedRequests(). - JournalMode.FULL - Records all incoming requests, both matched and unmatched.
1val mokksy = MokksyServer(
2 configuration = ServerConfiguration(
3 journalMode = JournalMode.FULL,
4 ),
5)Call resetMatchState() between scenarios to clear stub match state and the journal:
1@AfterTest
2fun afterEach() {
3 mokksy.resetMatchState()
4}1@AfterEach
2void afterEach() {
3 mokksy.resetMatchState();
4}Note: Stubs configured with
eventuallyRemove = trueare permanently removed from the registry on first match and cannot be re-armed byresetMatchState(). Re-register them before the next scenario.