If Context would be an interface instead of a class, it could easily be mocked in unit tests.
My router delegates certain paths to controllers that have methods which only take a Context object. If this would be an interface, I could mock it and only unit test my controller in isolation.
All the test examples I have found in this repository use full integration test that I would like to avoid (at least for my controller unit tests).
@grote why can't you mock it directly? Here's an example using mockk, are there some cases where this doesn't work?
import io.mockk.every
import io.mockk.mockk
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
class TestMockedContext {
@Test
fun `example test with mocked context`() {
val context = mockk<Context>()
every { context.path() } returns "Mocked path"
every { context.pathParam(any()) } returns "123"
assertThat(context.path(), `is`("Mocked path"))
assertThat(context.pathParam("A"), `is`("123"))
assertThat(context.pathParam("B"), `is`("123"))
}
}
Well, I am still using Java unit tests with Mockito which only mocks interfaces. You can specify a special ugly ClassImposterizer in case you really need to mock a class directly. However, this complains that Context is final.
I see. I'm against adding unnatural abstractions to simplify testing, but I understand why this would be frustrating. Maybe there are other mocking libraries in Java which supports using classes directly?
Just my two cents since we're using your awesome library and your latest update really and thoroughly broke our tests ;)
You removed the all-args-constructor from the Context, which is a final class, leaving it only with request and response thus disabling the per-test construction of a 'mock' context containing the path-params.
We'd need this mock to unit-test our handlers (get-by-id, delete-by-id for example).
So I'm stuck with various solutions:
Oh. Just saw that req and res are public fields as well. That would be a problem as well when mocking the Context (not so much when instancing it since I may pass them).
Hi @guFalcon, sorry for making your life harder!
Edit: I just want to clarify that I didn't mean that sarcastically. Javalin is intended to work well from Java, it wasn't my intention to make testing harder.
Is adding Kotlin and mockk as test dependencies not an option? It seems simpler than dealing with mockito.
I don't mind making Context non-final.
Path-params are created by comparing the request-uri to the path from the route declaration, It happens in the HandlerEntry class.
First of all: Don't get me wrong. You are great. Thank you so much for sharing this great library, it made life so much easier for us.
Since adding an additional testing tool isn't even an option, adding an additional testing tool AND a new language probably wouldn't be either.
Making the class non-final would help with Mockito2. At least a bit. The problem is that, even if you make it non-final, the request and response still are fields in this new implementation, as compared to properties with getter and setter in the last one.
Besides the more obvious reasons why one should never expose fields of a class publicly (encapsulation... I know that I'm old ;) ), I cannot intercept calls to one of those without using non-standard testing code (it's still doable with Mockito, but you wouldn't call that sort of code-to-be-tested well designed).
The goal of Mockito, as far as I get it, is to enable testing capabilities with minimal code, as long as you design your code right (that means testable). So the emphasis is not, like with other testing frameworks, on enabling tests in all kinds of structures (static fields in static classes for example). I think we should all strife for code that's easier to test.
Thx for the pointer in the right direction. It's no problem mocking the call to requestUrl (which is here https://github.com/tipsy/javalin/blob/a8714ea3a17bf3b119f939f0aacd68472f6fc9d5/src/main/java/io/javalin/Context.kt#L360 by the way) but I really cannot find the other stuff I'm supposed to mock for your code to parse the path-params correctly. It keeps telling me that the paramMap is empty.
So, in my humble opinion, with all due respect to the awesome maintainer :) it would be best to:
As for an explanation why I'm so nit picky about that stuff here, this class is a very central part of your implementation and that's why it should be easy to mock.
@guFalcon Don't worry, I appreciate the feedback. I'm not a big fan of having one specific Java library dictate Javalin's architecture, but as I understand mockito is hugely popular, so I'm willing to compromise. Please create a PR with your suggested changes, including a mockito test.
The reason path-params is giving your trouble is because the same context will have different path-params depending on which handler it's currently in.
Consider a GET to http://localhost:7000/123:
before("*", ctx -> ctx.pathParam("param")) // nothing
get("/:param", ctx -> ctx.pathParam("param")) // 123
after("*", ctx -> ctx.pathParam("param")) // nothing
As Javalin goes through all the declared handlers, it mutates the Context:
entry.handler.handle(ContextUtil.update(ctx, entry, requestUri))
You're welcome to include other changes to make testing easier, but try to keep them minimal (no interface for Context).
There's a Mockito trick for mocking final classes: https://www.baeldung.com/mockito-final
There's a Mockito trick for mocking final classes: https://www.baeldung.com/mockito-final
I don't mind making it non-final though. I really want to make it easier to use mockito, the only ting I want to avoid is the added interface.
That trick is using an experimental handler. First thing I tried. Comes with several downsides.
First, it's experimental. It doesn't even cover 20% of the originals functions.
Second, it's a switch that's in project scope. If you turn it on, it's on for the whole thing and because of 1. About 80% of our other tests are broken.
Nice thought though.
I fear there's no way around designing your code to be easy to test and nor should there be.
@guFalcon Could you show us some of the tests you have that are currently failing?
Edit: Since you said you used to use the constructor; I could add a method for creating a Context object:
@JvmStatic
@JvmOverloads
fun init(
request: HttpServletRequest,
response: HttpServletResponse,
matchedPath: String = "*",
pathParamMap: Map<String, String> = mapOf(),
splatList: List<String> = listOf(),
handlerType: HandlerType = HandlerType.INVALID
) = Context(response, request).apply {
this.matchedPath = matchedPath
this.pathParamMap = pathParamMap
this.splatList = splatList
this.handlerType = handlerType
}
Which could be called like this:
Context ctx = ContextUtil.init(req, res);
Context ctx = ContextUtil.init(req, res, "/:param");
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams);
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats);
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats, handlerType);
Sorry for the delay. Weekend @ home with kids&wife... no spare-time at the moment :)
Although... if you'd like something in Java, I'm all in for it... but my Kotlin-skills are somewhat limited (non existent :) )
Yes. That init-method definitely would remove all the pain.
Sounds like a good solution for now.
That said and just to be clear on that matter, my opinion still is that such a central class like the Context should never be final and no object should expose public fields except under very rare circumstances.
The tests are for a handler-implementation... like so:
@Test
public void deleteCallsDeleteCorrectly() {
group.deleteEntry(TestUtil.getContext(request, response, ImmutableMap.of(":id", "1")));
verify(dao).delete(eq(1L), eq(1L), any());
verify(response).setStatus(204);
}
@guFalcon Yes. That init-method definitely would remove all the pain. Sounds like a good solution for now.
Great, I'll merge that function and release a new version later today.
That said and just to be clear on that matter, my opinion still is that such a central class like the Context should never be final and no object should expose public fields except under very rare circumstances.
Noted, but that is the exact opposite of Kotlin's philosophy, where classes are final by default and fields (properties) are public by default. These particular fields are also final, so I don't see what you gain from calling them as methods (except then you have to check what the method does).
Nice. Thx. Fair enough.
As for the fields... You would have a getter for those, without a setter... and you could mock a call to that getter (with the class being not-final). That's all.
You could write:
(from the top of my head - in a text-editor)
HttpServletRequest req = mock(HttpServletRequest.class);
HttpServletResponse res = mock(HttpServletResponse.class);
Context ctx = mock(Context.class); // cannot do that with a final class
when(ctx.req()).thenReturn(req); // cannot do that with a field
when(ctx.res()).thenReturn(res);
when(ctx.pathParam(anyString()).thenReturn("123");
ContextUtil.init(req, res, ...); has been released as part of 2.1.0: https://javalin.io/news/2018/08/27/javalin-2.1.0-released.html
Nice. Thx. Still waiting for maven.org re-indexing... But I think you can resolve this for now as I'm perfectly capable of mocking the Context with Mockito for now.
Should be done: https://repo1.maven.org/maven2/io/javalin/javalin/
@grote Are you satisfied with this approach?
Yep, thanks for your work @tipsy!
HttpServletRequest and HttpServletResponse, then pass them to ContextUtil.init(). The init function is overloaded to take all the dependencies of Context: Context ctx = ContextUtil.init(req, res);
Context ctx = ContextUtil.init(req, res, "/:param");
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams);
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats);
Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats, handlerType);
Still not there https://search.maven.org/search?q=a:javalin
But it can take a while.
... works now. :1st_place_medal:
Sorry for warming this ticket up again. I came across a case where I need to mock formParams, but since Context still is final, I need to deal with ServletInputStream resp. HttpInput
What was the reason again for not opening up Context?
@grote I was under the impression that adding the util-methods would be enough, but I don't mind opening the class, I just don't want to do it for no reason. I'll open it now, but it would be helpful if you could submit a PR with a few tests that take advantage of the fact that the class is open.
Wow @tipsy, you are awesome! :D
Thanks for the quick change!
What kind of tests are you thinking of? Do you want to introduce a new test dependency like jMock that tests an example controller?
Wow @tipsy, you are awesome! :D Thanks for the quick change!
No problem!
What kind of tests are you thinking of? Do you want to introduce a new test dependency like jMock that tests an example controller?
That sounds fine. I mainly want you to build the project locally and write a test which covers all your needs (you can make additional changes to Javalin, as long as those changes don't include a single-implementation interface). I think doing this would be easier than waiting for a new version and requesting changes. Speaking of new versions, I plan on releasing 2.1.1 on Sunday, so if you have anything else you want included now is the time!
open Context has been released as part of 2.1.1. It should be available soon.
Hey guys, sorry to revive an old issue - I'm having issues getting mocking working using Spring mocking. The following junit test should return a 400 error but returns a 200. On closer inspection it appears that the context ctx doesn't include the query parameter "limit":
/**
* Test invalid limit param
*/
@Test
public void testGetAllInvalidParam() {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setParameter("limit", "invalid");
MockHttpServletResponse res = new MockHttpServletResponse();
Context ctx = ContextUtil.init(req,res);
system.out.println(ctx.queryParamMap()); // prints {}
system.out.println(ctx.req.getParameterMap()); // prints "limit" in map
// Logservice implements CrudHandler
LogService service = new LogService();
service.getAll(ctx);
assertEquals(HTTP_BAD_REQUEST, ctx.status()); // this should fail because the service.getAll(ctx) method can't find "limit" in ctx.queryParamMap().
}
I feel like i'm missing something obvious here. Any help would be appreciated :)
@bsecker queryParam() fetches params from the queryString of the request, I'm guessing setParameter() doesn't change the queryString ? I haven't heard of setParameter() before, and I can't seem to find it in the spec.
thanks for your help!
For anyone else, replacing setParameter() with setQueryString("limit=51") fixed the test for me. docs
Context ctx = ContextUtil.init(req, res); Context ctx = ContextUtil.init(req, res, "/:param"); Context ctx = ContextUtil.init(req, res, matchedPath, pathParams); Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats); Context ctx = ContextUtil.init(req, res, matchedPath, pathParams, splats, handlerType);Sorry for coming back to this again - I'm trying to unit test the below method but can't work out how to initialise
ctxwith a particular request body for my test.
public void createSomething(Context ctx) {
MyObject obj = ctx.bodyValidator(MyObject.class)
.check(o -> validate(o), "error message")
.get();
// ......
}
I was hoping ContextUtil.init() would accept a request body. What am I missing?
FYI to anyone else stumbling upon this via the same route I did:
Tried mocking with mockito + Junit5:
Context mockContext = mock(Context.class);
This line will generate an NullPointerException:
when(mockContext.body()).thenReturn(gson.toJson(testResult));
Because mockito silently fails when you try to mock a final method (took me a bit to link the NPE to the comments above about final method)
I wanted to avoid "tricks" so I didn't look into @grote's link. But it turns out the trick is just an opt-in mockito2 (and in 3 as well) feature to allow mocking of final methods:
https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable
Which worked fine.
I dunno what all the talk about using interfaces instead of classes was about - this worked fine for me, and I've been mocking (non-final) classes with mockito just fine for at least a couple years now...
And agree that, even in Java, having a 1-implementation interface is clusters up source trees, and obfuscates code with unnecessary abstractions. It was a trend for a while, but for new code, I'm mostly seeing it die out (thank goodness!). But it does still stick around here & there. Note this thought only applies if there is 1 and only 1 implementation.
Of course, PowerMock is an option, but that doesn't work with JUnit5. In general, it looks like the PowerMock project is slowly dying for some reason..
I dunno what all the talk about using interfaces instead of classes was about - this worked fine for me, and I've been mocking (non-final) classes with mockito just fine for at least a couple years now...
That's great to hear! Would you be willing to write a short tutorial for mocking in Java for https://javalin.io/tutorials/ ?
Yup. Gotta do it off work hours, but yeah.
This coming weekend should be fairly free.
Is there a repo with tutorials that I could issue a PR against?
Thanks for doing this! The tutorials can be found here: https://github.com/javalin/javalin.github.io/tree/master/_posts/tutorials
You just have to add a new file, written in markdown (or HTML).
Rough Draft PR for docs:
https://github.com/javalin/javalin.github.io/pull/54/files
Example Javalin+Mockito Project:
https://gitlab.com/stuAtGit/javalinmockitoexample
I got a little invested in the example project, so I didn't get as many details into the docs as I would like.
Also, I should probably add some screenshots too.
Thank you very much @StuAtGit, looks good! You can remove the "this aspect of Javalin may change" part, I don't think it's any more likely to change than the rest of the codebase.
Cool. I never got the screenshots, but removed the "may change". Suppose it's good enough. Worse is better, after all ;)
Thank you, it's live now at https://javalin.io/tutorials/mockito-testing :)
I wanted to share my solution to this. Maybe it's just me but the mock-maker-inline solution was still throwing odd errors. This builder class mocks the HttpServletRequest and HttpServletResponse then uses ContextUtil.init to create a true Context object.
Source
MockContextBuilder.kt
import com.nhaarman.mockitokotlin2.doAnswer
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.stub
import io.javalin.http.Context
import io.javalin.http.util.ContextUtil
import java.io.InputStream
import java.util.Collections
import javax.servlet.ReadListener
import javax.servlet.ServletInputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* Test utility for creating a mock [Context]
*/
class MockContextBuilder(matchedPath: String = "", pathParamMap: Map<String, String> = mapOf()) {
val req: HttpServletRequest = mock()
val res: HttpServletResponse = mock()
val ctx = ContextUtil.init(req, res, matchedPath, pathParamMap)
init {
req.stub { on { headerNames } doReturn Collections.emptyEnumeration() }
body("")
method("GET")
path("http://localhost")
}
/**
* Return the built [Context]
*/
fun build(): Context {
return ctx
}
/****************************************************************************************************
* Builder functions
****************************************************************************************************/
/**
* Set the body on the mock [HttpServletResponse]
* Uses doAnswer instead of doReturn so the bytestream can be read multiple times
*/
fun body(value: String): MockContextBuilder {
req.stub { on { inputStream } doAnswer { DelegatingServletInputStream(value.byteInputStream()) } }
return this
}
/**
* Set the contentType on the mock [HttpServletResponse]
*/
fun contentType(value: String): MockContextBuilder {
res.stub { on { contentType } doReturn value }
return this
}
/**
* Set a single header on the mock [HttpServletRequest]
*/
fun header(key: String, value: String): MockContextBuilder {
req.stub { on { getHeader(key) } doReturn value }
return this
}
/**
* Set the method on the mock [HttpServletRequest]
*/
fun method(value: String): MockContextBuilder {
req.stub { on { method } doReturn value }
return this
}
/**
* Set the body on the mock [HttpServletRequest]
*/
fun path(value: String): MockContextBuilder {
req.stub { on { requestURI } doReturn value }
return this
}
/**
* Create a DelegatingServletInputStream for a given stream
* Trying to mock an inputStream directly leads to an infinite loop because it calls `read` forever
*/
inner class DelegatingServletInputStream(val sourceStream: InputStream) : ServletInputStream() {
override fun isReady(): Boolean {
return true
}
override fun isFinished(): Boolean {
return true
}
override fun setReadListener(readListener: ReadListener?) {}
override fun read(): Int {
return this.sourceStream.read()
}
override fun close() {
super.close()
this.sourceStream.close()
}
}
}
Usage
Example with headers and body:
val ctx = MockContextBuilder()
.header("Accept", "application/json")
.header("Authorization", "Basic test1234567890")
.body(toJson(mapOf(
"domain" to "test",
"connectionType" to "ORACLE")))
.build()
You can't assert on the ctx itself (e.g. ctx.status()) but you can use req and res.
val ctx = MockContextBuilder().build()
val handler = NotFoundHandler()
handler.handle(ctx)
verify(ctx.res).setStatus(NOT_FOUND_404)
hi @tipsy!
This code fails to me:
val context = mockk<Context>()
every { context.path() } returns "Mocked path"
every { context.pathParam(any()) } returns "123" <- ERROR
assertThat(context.path(), `is`("Mocked path"))
assertThat(context.pathParam("A"), `is`("123"))
assertThat(context.pathParam("B"), `is`("123"))
Error:
Parameter specified as non-null is null: method io.javalin.http.util.ContextUtil.pathParamOrThrow, parameter pathParams
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method io.javalin.http.util.ContextUtil.pathParamOrThrow, parameter pathParams
at io.javalin.http.util.ContextUtil.pathParamOrThrow(ContextUtil.kt)
at io.javalin.http.Context.pathParam(Context.kt:195)
I'm using Javalin(3.10.1), Speck(2.0.11), mockk(1.10.2), Kotlin(1.3.7). and Java 14
Any ideas?
Thanks!
Hi @acmattos. Your test seems to work here:

I think I have a weird combination here...
Sometimes I got this:

Sometimes this:


Expecting code not to raise a throwable but caught
<"java.lang.IllegalArgumentException: Parameter specified as non-null is null: method io.javalin.http.util.ContextUtil.pathParamOrThrow, parameter pathParams
at io.javalin.http.util.ContextUtil.pathParamOrThrow(ContextUtil.kt)
at io.javalin.http.Context.pathParam(Context.kt:195)
at context.ContexTest$1$1$1$2$1$1.invoke(ContextTest.kt:23)
at context.ContexTest$1$1$1$2$1$1.invoke(ContextTest.kt:12)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:24)
at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithNPERethrow$1.invoke(RecordedBlockEvaluator.kt:74)
at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:36)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalEvery(API.kt:92)
at io.mockk.MockKKt.every(MockK.kt:98)
at context.ContexTest$1$1$1$2$1.call(ContextTest.kt:23)
at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:62)
at org.assertj.core.api.AssertionsForClassTypes.catchThrowable(AssertionsForClassTypes.java:750)
at org.assertj.core.api.AssertionsForClassTypes.assertThatCode(AssertionsForClassTypes.java:721)
at org.assertj.core.api.Assertions.assertThatCode(Assertions.java:1140)
at context.ContexTest$1$1$1$2.invoke(ContextTest.kt:22)
at context.ContexTest$1$1$1$2.invoke(ContextTest.kt:12)
at org.spekframework.spek2.runtime.scope.TestScopeImpl.execute(scopes.kt:136)
at org.spekframework.spek2.runtime.Executor$execute$result$2$exception$1$job$1.invokeSuspend(Executor.kt:74)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Using these dependencies:
implementation "io.javalin:javalin:3.10.1"
testImplementation "org.spekframework.spek2:spek-dsl-jvm:2.0.13"
testRuntimeOnly "org.spekframework.spek2:spek-runner-junit5:2.0.13"
testImplementation "io.mockk:mockk:1.10.2"
testImplementation "org.assertj:assertj-core:3.16.1"
And the sample project:
context.zip
In my case, I am not running Javalin server. I'm just trying to mockk Context object @tipsy.
Am I doing it in the wrong way?
Am I doing it in the wrong way?
It looks correct, I'm not sure what's going on 馃
I don't think we've gotten any other complaints on using mockk.
Anyone else here have an idea?
I've seen this before, I believe it has something to do with how javalin is compiling in kotlin. It's been a while, but I think it was something to do with the pathParam method being overloaded by a kotlin-generated one or something to that effect. Anyway, you should be able to get it to work by using the version that returns a validator.
So, you would change
every { context.pathParam(any()) } returns "123"
to
every { context.pathParam(any(), String::class.java) } returns Validator<String>("123")
Your corresponding server code would also need to change to use that version, passing in that same String class and calling get on the returned validator.
It's possible something may have been updated to not require this, anymore, but that's the quick fix I found before that seems to still work.
Note: Oh, and as I recall, the compiled method used wasn't being chosen deterministically, so sometimes it failed with that exception, but sometimes it worked just fine.
@trognus thanks for the tip! It's not the thing that I need, but I'll do it as recommended, as much as I can.
In some places, it will not be possible to follow that way. But, it works indeed :)
Most helpful comment
For people arriving here wondering how to do mocking with Mockito
HttpServletRequestandHttpServletResponse, then pass them toContextUtil.init(). The init function is overloaded to take all the dependencies ofContext: