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)
})
)
}
Related
I have an Apache Camel application, which uses a Choice with a Predicate. How can I test the predicate without an integration test?
Code
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
public EndpointRouteBuilder route() {
return new EndpointRouteBuilder() {
#Override
public void configure() throws Exception {
from(file("d:/tmp/camel/"))
.choice()
.when(jsonpath("$[?(#.status == 0)]"))
.log("ok")
.otherwise()
.log("not ok");
}
};
}
}
Research
I read Test JUnit5, but it looks like an integration test. However, I don't want to test a full route.
I read Test Spring JUnit5, but it is an integration test.
Question
How can I extract the predicate jsonpath("$[?(#.status == 0)]") and test it isolated in an unit test with JUnit 5?
Might be hard to accomplish this without a CamelContext. I'd probably approach this with Camel's excellent mocking and stubbing utilities. But if you really just want to isolate the jsonpath expression, you could try something like this:
JsonPathExpression exp = new JsonPathExpression("$[?(#.status == 0)]");
Exchange exchange = new DefaultExchange(context);
final Predicate predicate = exp.createPredicate(context);
exchange.getIn().setBody("{ \"status\": 0 }");
final boolean matches = predicate.matches(exchange);
assertTrue(matches);
Note that you'll still need a CamelContext for this. Typically you'd get it by having the test class extend CamelTestSupport, or if you're in a spring environment, spring can autowire it: #Autowired CamelContext camelContext;
Edit: If you just want to test the JsonPath expression outside of Camel:
String jsonPath = "$[?(#.status == 0)]";
String json = "{ \"status\": 0 }";
DocumentContext jsonContext = JsonPath.parse(json);
JSONArray result = jsonContext.read(jsonPath);
assertEquals(1, result.size());
My opinion (you'll probably get 100 more ;-)
Separate that route into another class by itself that can be loaded into the Spring context later.
Use CamelTestSupport to load just Camel (not Spring) in JUnit.
Use Camel "advice" to change "from" to a direct, or create a file (in your test) to exercise the test case you want (once with each branch of the choice.
Again with "advice" change the log to mocks - then after running the file/message you want check to see if the correct mock got a message and the other did not.
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.
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!
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'm sure this is a fairly common situation. I'm using the Spring Security Core plugin and want to create a domain model that has a Person limited to certain roles:
class Workgroup {
Person manager
...
static constraints = {
manager(validator: {mgr ->
// it feels like there should be a more elegant, groovy way of doing this.
def auths = mgr.getAuthorities();
def returny = false
auths.each {
if(it.authority == 'ROLE_MANAGER')
{
returny = true
}
}
return returny
})
}
}
This test fails like a mofo:
void testInvalidManager() {
def nick = new Person(username:'Nick')
def nonManagerRole = new Role(authority:'ROLE_EMPLOYEE')
UserRole.create(nick,nonManagerRole)
def awesome = new Workgroup(name:'mooCows', manager:nick)
mockForConstraintsTests(Workgroup, [awesome])
assertFalse awesome.validate()
assertEquals "validator", awesome.errors["manager"]
}
testInvalidManager Error No signature of method: users.UserRole.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false, insert:true]] Possible solutions: wait(), any(), wait(long), use([Ljava.lang.Object;), isCase(java.lang.Object), each(groovy.lang.Closure)
groovy.lang.MissingMethodException: No signature of method: users.UserRole.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false, insert:true]]
Possible solutions: wait(), any(), wait(long), use([Ljava.lang.Object;), isCase(java.lang.Object), each(groovy.lang.Closure)
at users.UserRole.create(UserRole.groovy:32)
at users.UserRole.create(UserRole.groovy)
at users.UserRole$create.call(Unknown Source)
at users.WorkgroupTests.testInvalidManager(WorkgroupTests.groovy:17)
Is this better covered in Integration than Unit Testing? Do I need to mock UserRole (if so, how?)? How are these types of tests normally done?
UserRole.create() calls save(), so you need to use mockDomain() instead of just mockForConstraintsTests().
But that's only if you're ok with testing the domain model with mocks, which I would never do. The mocking support in Grails should be used when testing Controllers or other classes that use domain classes but shouldn't be bothered with real persistence, creating a database (even in-memory), etc. By removing that dependency you're concentrating on the current tier, trusting that the other tier is already properly tested. But when you use mocking to test domain classes, you're really just testing the mocking framework. So I always use integration tests for domain classes so they run against a real database.
To answer the implicit question from your code example, I'd write the constraint as
static constraints = {
manager validator: { mgr ->
mgr.authorities.find { it.authority == 'ROLE_MANAGER' } != null
}
}
The issue with its bulk is that you're using each() when a regular for loop would be preferable since you can return from a for loop. Use each() only when you really want to invoke the closure on every instance. Here's one that's less groovy than the other one but uses a for loop:
static constraints = {
manager validator: { mgr ->
for (auth in mgr.getAuthorities()) {
if (it.authority == 'ROLE_MANAGER') {
return true
}
}
return false
}
}