Later Edit
I ended up to have my api service methods suspended and refactor my code as suggested by #LordRaydenMK.
The reason for using the library ru.gildor.coroutines:kotlin-coroutines-retrofit it the first place was out of pure convenience AND it was before retrofit released the version which would support for coroutines.
Original Question
I have been trying for a couple of days to mock the API calls with no success. I'm using the following libraries:
retrofit - i think we are all familiar with it
ru.gildor.coroutines:kotlin-coroutines-retrofit - for a couple of useful coroutine extensions
io.mockk:mockk - for mocking
It is a simple case of mocking the API response
interface ApiService {
#GET
fun getMyData(#Header("x-value") myValue: String): Call<String>
}
class Usecase(api: ApiService) {
suspend fun execute() {
val result = api.getMyData(value: String).awaitResult()
// do smth with that result for now just return it
return (result as Result.Ok).value
}
}
class UseCaseTest {
private val api = mockk<ApiService>()
#Test
fun testApiCall() {
coEvery { api.getMyData(any()) } returns CallTest.buildSuccess("you did it!")
val result = useCase.execute()
assertEquals(result, "you did it!")
}
}
In the example above the test hangs on the awaitResult extension method.
What i have tried so far with no luck:
mockkStatic(ru.gildor.coroutines.retrofit.CallAwait) but with no success
mockk Call<String> and do a coEvery { mockedCall.awaitResult() } returns ....
I'm sure it's something simple that I'm missing and a pair of fresh eyes will spot it from a mile away.
First thing:
getMyData is NOT a suspend function so probably you should NOT be using coEvery when mocking it (tho I'm not a Mockk user).
That being said, Retrofit does support suspend functions natively, so you could do:
interface ApiService {
#GET
suspend fun getMyData(#Header("x-value") myValue: String): String
}
that means no need for awaitResult in your use case. In this scenario you do need coEvery when mocking it.
Related
UserController:
class UserController(private val graphRepository: GraphRepository) : Controller {
override fun installRoutes(router: Router) {
router.install {
post("/api/v1/user").handler(this#UserController::addUser)
}
}
}
Testing route and calling route handler "addUser":
#Test
fun newUserAdded() {
Mockito.`when`(mockRoutingContext.queryParam("id")).thenReturn(listOf("1"))
Mockito.`when`(mockGraphRepository.getUser("1")).thenReturn(Promise.ofSuccess(null))
Mockito.`when`(mockGraphRepository.enrollUser(any())).thenReturn(Promise.ofSuccess(Unit))
Mockito.`when`(mockRoutingContext.response()).thenReturn(mockHttpServerResponse)
Mockito.doNothing().`when`(mockHttpServerResponse).end()
UserController(mockGraphRepository).addUser(mockRoutingContext)
Mockito.verify(mockRoutingContext, Mockito.times(1)).response()
Mockito.verify(mockHttpServerResponse).end()
}
The main question is how to test the controller route without explicitly calling "addUser" on "UserController" because I want to make the controller function private.
Mocking behavior for types you don't own is generally discouraged for a variety of reasons, such as (but not limited to):
If the real implementation of the mocked dependency changes, the mock's behavior will not automatically reveal any forward-breaking changes.
The more mocks a test introduces, the more cognitive load the test carries, and some tests require a lot of mocks in order to work.
The approach that works best for me is to think of these more as integration tests, and avoid the mocks all together.
To achieve this, I've got an abstract VertxPlatform class that I extend that contains references to resources I commonly refer to across a variety of tests:
the Vertx instance itself
a Router
an EventBus
an HttpServer
a WebClient
These resources is reinitialized per invocation of each test, and the Router is subsequently associated with the HttpServer.
A typical test ends up looking something like this:
class MyHandlerIT : VertxPlatform() {
private lateinit var myHandler: MyHandler // <-- the component under test
#Before override fun setUp(context: TestContext) {
super.setUp(context) // <-- reinitializes all the underlying Vert.x components
myHandler = MyHandler()
router.post("/my/handler/path")
.handler(myHandler.validationHandler())
.handler(myHandler.requestHandler(vertx))
.failureHandler(myHandler.failureHandler())
}
#After override fun tearDown(context: TestContext) {
super.tearDown(context)
}
#Test fun status_400_on_some_condition(context: TestContext) {
val async = context.async()
testRequest(POST, path = "/my/handler/path", params = null, body = null, headers = null)
.subscribeBy(
onSuccess = { response ->
context.assertEquals(BAD_REQUEST.code(), response.statusCode())
async.complete()
},
onError = { error ->
context.fail(error)
}
)
}
}
In each individual test you might have some more case-specific setup. For example, if MyHandler gets results from your GraphRepository via the EventBus you could setup a fake Consumer within the scope of that test that replies with a pre-canned result that server back the values you were otherwise trying to mock.
Hope this helps, or at least inspires some thought!
I have a problem that should be relatively straight-forward but I find myself going into a deep rabbit hole
I would like to Unit Test my call to Elasticsearch - with the search request including the aggs. What is a good way to go about mocking the response?
Elasticsearch RestHighLevelClient is very complex ... one has to deal with the complex web of XContentType/XContentType parser call chains
Is there a simple way to mock the call? I have sample JSON responses that one would receive if we called ES from Kibana Devtools
private fun searchResponseFromContent(content: String): SearchResponse {
val xContentType = XContentType.JSON
val parser = xContentType.xContent().createParser(
NamedXContentRegistry.EMPTY, // this would not handle aggrgations
null,
content
)
return SearchResponse.fromXContent(parser)
}
Generally speaking do people just not test Elasticsearch calls in their unit test? There doesn't seem to be any good solutions to mock calls to ES
The answer is just simplify what RestHighLevelClient is doing internally:
private fun searchResponseFromContent(content: String): SearchResponse {
val xContentType = XContentType.JSON
val parser = xContentType.xContent().createParser(
NamedXContentRegistry(namedXContentRegistry()),
null,
content
)
return SearchResponse.fromXContent(parser)
}
private fun namedXContentRegistry(): List<NamedXContentRegistry.Entry> {
// add as needed from RestHighLevelClient:1748 on version 7.3.2
// static List<NamedXContentRegistry.Entry> getDefaultNamedXContents()
return listOf(
NamedXContentRegistry.Entry(Aggregation::class.java, ParseField(HistogramAggregationBuilder.NAME), ContextParser { p, c ->
ParsedHistogram.fromXContent(p, c as String)
})
)
}
This is the method to test:
It gets an URL and return a json after sending a GET request. It is a plain function which sits in a package rather than a method from a class. Same case for the extension method below.
fun getJson (url: String): String {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = "GET"
return connection.getResult()
}
This is the extension method:
It will start connecting and read from result stream.
internal fun HttpURLConnection.getResult(charset: Charset = Charsets.UTF_8): String {
this.connect()
return this.inputStream.bufferedReader(charset).use { it.readText() }
}
This is the test case:
I tried to mock the HttpURLConnection that is about to be used here and call the original method, then just call the method and assert whether the mock has been set with the expected value.
class Spike {
#Test
fun test_getJson() {
val expectedResult = "{ok: true}"
val mockConnection = mock(HttpURLConnection::class.java)
Mockito.`when`(mockConnection.getResult()).thenReturn(expectedResult)
getJson("http://www.google.com")
assertEquals("GET", mockConnection.requestMethod)
assertEquals("http://www.google.com", mockConnection.url.host)
}
}
This is the error
java.lang.IllegalStateException: this.inputStream must not be null at
my.spike.pack.http.UtilsKt.getResult(utils.kt:45)
It just like the mock is not working.
How to solve this without changing the signature of the getJson function?
This will not work because of the way Kotlin extension methods are implemented on the class / bytecode level.
What you see in source code is HttpURLConnection.getResult but on the class/bytecode level there is another file created with a static method: public final static getResult(HttpURLConnection, Charset).
Mockito cannot mock static methods. If you really have to mock one, then I think PowerMock is capable of doing that.
Edit:
If you have a module wide function then it is also generated on a class. Assuming you have a file StreamFunctions.kt with a function: doSomething then, there will be (by default) generated class StreamFunctionsKt with a static function doSomething. More details can be found here: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html
That should be as easy as
Mockito.`when`(mockConnection.inputStream).thenReturn(ByteArrayInputStream("test".toByteArray()))
I have an ExampleModel that calls to an ExampleService that retrieves data from our backend. I can't figure out how to write unit tests for my application; which is structured as shown below:
ExampleService
public function retrieveMyToDoList(parameters):Promise
{
var promise:Promise = performRequest({request: "call to backend", parameters: values, session_id: clientModel.sessionID});
promise.addResultProcessor(parseRetrieveToDoListResult);
return promise;
}
protected function parseRetrieveToDoListResult(data:Object, callback:Function):void
{
does some JSON parsing into an object
callback(null, object containing my retrieved data)
}
ExampleModel
public function getMyToDoList():Promise
{
var promise:Promise = exampleService.retrieveToDoList(parameters);
promise.addResultHandler(onGetToDoListResult);
promise.addErrorHandler(onGetToDoListError);
return promise;
}
private function onGetHeadrsByUserResult(promise:Promise):void
{
// where this event will be listened to by mediators etc
dispatchEvent(new ResponseEvent(GOOD_RESULT));
}
private function onGetHeadrsByUserError(promise:Promise):void
{
dispatchEvent(new ResponseEvent(BAD_RESULT));
}
I'm trying to use asmock to mock my Service so that I can test my Model and how it handles the various results in the resulting Object but how do I mock the callback? I saw examples where the return values were mocked but in my case I'm using the Promise and callback and I'm not too sure how to go ahead.
If someone could please advise.
Thanks!
You can let the mock service return a real promise and call the handleResult method of the promise directly.
FYI: it's not a good idea to have a direct dependency from the model to the service. You should let the service manipulate the model, or pass the results from the service to a command which will manipulate the model. Models should never depend on anything else than helper classes.
I have code piece sending jms messages via Spring JMSTemplate. For testing the the method i use Mockito.
My code looks like following.... publishDialogueServiceMessage()->
brokerUrl = jmsQueueProperties.getProperty(MessageRouterConstants.JMS_QUEUE_URL);
LOG.info("The broker url is : {}", brokerUrl);
jmsTemplate.send(jmsQueueProperties.getProperty(MessageRouterConstants.QUEUE), new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
ObjectMessage obj = session.createObjectMessage(serviceResponse);
messageSent = true;
return obj;
}
});
In above code to i set boolean variable true, to check that if the message is sent
My Test looks following,
#Before
public void setUp() throws Exception {
connectionFactory = Mockito.spy(new ActiveMQConnectionFactory(
"vm://localhost?broker.persistent=false"));
conn = connectionFactory.createConnection();
conn.start();
}
#After
public void cleanUp() throws Exception{
conn.stop();
}
#Test
public void testPublishDialogueServiceMessage()
{
ServiceResponse response = Mockito.mock(
ServiceResponse.class, Mockito.withSettings()
.serializable());
JmsTemplate mockTemplate = Mockito.mock(JmsTemplate.class);
java.util.Properties p = Mockito.mock(java.util.Properties.class);
Mockito.when(p.getProperty(MessageRouterConstants.QUEUE))
.thenReturn("outbound.request.queue");
mockTemplate.setConnectionFactory(connectionFactory);
mockTemplate.setDeliveryPersistent(true);
mockTemplate.setSessionAcknowledgeMode(2);
mockTemplate.setSessionTransacted(true);
ReflectionTestUtils.setField(publisher, "jmsQueueProperties", p);
ReflectionTestUtils.setField(publisher, "jmsTemplate", mockTemplate);
// test
publisher.publishDialogueServiceMessage(response);
ArgumentCaptor<MessageCreator> msgCreator = ArgumentCaptor.forClass(MessageCreator.class);
Mockito.verify(p, Mockito.times(2))
.getProperty(Mockito.anyString());
Mockito.verify(mockTemplate, Mockito.times(1)).send(
Mockito.anyString(), Mockito.any(MessageCreator.class));
//MessageCreator msgCrt = Mockito.spy(msgCreator.getValue());
//Assert.notNull(msgCrt);
Assert.isTrue(publisher.isMessageSent());
}
In test i facing an interesting problem as publisher.isMessageSent() always returns me FALSE indicating that send message seems not executed(?). but Mockito.verify(mockTemplate, Mockito.times(1)).send(Mockito.anyString(), Mockito.any(MessageCreator.class)); goes fine.
I am wondering what is the cause that my messageSent variable not setting. Can anyone shed some light what I might be doing wrong.
Simple, you have a mock for the jmsTemplate (your mockTemplate). When a method is invoked on a mock it doesn't do anything other than record the call to the mock. So the mock doesn't know that it should attempt to invoke the msgCreator.
Looking at your test I see some obvious issues that suggest a lack of knowledge of Mockito. Why are you setting all of those fields on mockTemplate? It is a mock, it will not use those fields anyway. This also suggests that you don't need the code in your #Before and #After.
If you REALLY want your test to send a message via JMS (and thereby invoke the message createor) you should use a spy on JmsTemplate instead of a mock. However, I would highly discourage this as your test will be dependent on an external system and you would in effect be testing JsmTemplate. The fact that your mock gets invoked properly is sufficient. The only additional thing I think you need to do is to invoke the message creator being passed to the mock to verify that it creates the message correctly.