Why is Koin scoping feature not working properly? - unit-testing

So scoping with Koin DI seem to throw a weird exception when KoinApplication::checkModules() method is called within a unit test. Here is the full code:
import org.koin.core.KoinApplication
import org.koin.core.component.KoinComponent
import org.koin.core.component.KoinScopeComponent
import org.koin.core.component.createScope
import org.koin.core.component.inject
import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import org.koin.core.scope.Scope
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.check.checkModules
import org.koin.test.inject
import kotlin.test.BeforeTest
import kotlin.test.Test
class FixScopingTest : KoinTest {
private val component1: Component1 by inject()
private lateinit var koinApp: KoinApplication
#BeforeTest
fun setup() {
koinApp = startKoin {
modules(
module {
single { Component1() }
scope<Component1> {
scoped { Component2() }
}
}
)
// printLogger(Level.DEBUG)
}
}
#Test
fun verifyKoinApp() {
//component1.component2.print()
koinApp.checkModules()
}
}
class Component1 : KoinComponent, KoinScopeComponent {
override val scope: Scope by lazy { createScope(this) }
val component2: Component2 by inject()
}
class Component2 {
fun print() = println("Component2::print()")
}
exception 1:
com.xycompany.xyproj.xypackage.FixScopingTest > verifyKoinApp FAILED
java.lang.IllegalStateException: Missing MockProvider. Please use MockProvider.register() to register a new mock provider
at org.koin.test.mock.MockProvider.getProvider(MockProvider.kt:10)
at org.koin.test.mock.MockProvider.makeMock(MockProvider.kt:23)
at org.koin.test.check.CheckModulesKt.mockSourceValue(CheckModules.kt:102)
at org.koin.test.check.CheckModulesKt.check(CheckModules.kt:95)
at org.koin.test.check.CheckModulesKt.checkAllDefinitions(CheckModules.kt:86)
at org.koin.test.check.CheckModulesKt.checkModules(CheckModules.kt:72)
at org.koin.test.check.CheckModulesKt.checkModules(CheckModules.kt:40)
at org.koin.test.check.CheckModulesKt.checkModules$default(CheckModules.kt:40)
at com.xycompany.xyproj.xypackage.FixScopingTest.verifyKoinApp(FixScopingTest.kt:43)
Second weird issue appears when you uncomment the commented part so we would have usage of scoped components on DEBBUG level logger:
exception 2:
com.xycompany.xyproj.xypackage.FixScopingTest > verifyKoinApp FAILED
java.lang.NoSuchMethodError: 'double kotlin.time.Duration.toDouble-impl(long, java.util.concurrent.TimeUnit)'
at org.koin.core.time.MeasureKt.measureDurationForResult(Measure.kt:41)
at org.koin.core.scope.Scope.get(Scope.kt:189)
at com.xycompany.xyproj.xypackage.FixScopingTest$special$$inlined$inject$default$1.invoke(KoinTest.kt:53)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.xycompany.xyproj.xypackage.FixScopingTest.getComponent1(FixScopingTest.kt:20)
at com.xycompany.xyproj.xypackage.FixScopingTest.verifyKoinApp(FixScopingTest.kt:41)
SETTINGS:
Kotlin Multiplatform Project (test is run in both Andorid and Common packages with the same problem)
VERSIONS:
koin-core: 3.1.3
koin-android: 3.1.3

Looks like you need to add a MockProviderRule when using scoped. It's not required if are not using scopes.
#get:Rule
val mockProvider = MockProviderRule.create { clazz ->
// Mock with your framework here given clazz
// e.g: Mockito.mock(clazz.java)
}
And for it to work you also need to add this dependency to gradle
testImplementation "io.insert-koin:koin-test-junit4:3.1.6"
https://insert-koin.io/docs/reference/koin-test/checkmodules/#allow-mocking-with-a-junit-rule

Related

How can I run my custom gradle task in the unit test?

I need to run my gradle task to test basic functional in the unit test:
import org.gradle.api.Project;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.Test;
public class IwillfailyouPluginTest {
#Test
public void applyPlugin() {
final Project project = ProjectBuilder.builder().build();
project.getPlugins().apply(IwillfailyouPlugin.class);
project.task("iwillfailyou").// what method should I run?
}
}
But I can not find the method to run it. Help me, please
My understanding is that ProjectBuilder is more for unit-like tests. So with what you have, you should only be asserting that a task named iwillfailyou exists, is of a certain type, and has the correct configuration.
public class IwillfailyouPluginTest {
#Test
public void applyPlugin() {
final Project project = ProjectBuilder.builder().build();
project.getPlugins().apply(IwillfailyouPlugin.class);
assertTrue(project.getTasks().getNames().contains("iwillfailyou"));
MyCustomTaskType iwillfailyou = project.getTasks().getByName("iwillfailyou");
assertEquals(123, iwillfailyou.getSomeConfig())
}
}
It looks looks you're trying to test the behavior/function of the custom task. For that sort of test, you would use TestKit.
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.io.FileWriter;
import java.nio.file.Files;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.BuildResult;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class IwillfailyouPluginFunctionalTest {
#Test
void canRunTask() throws IOException {
// Setup the test build
File projectDir = new File("build/functionalTest");
Files.createDirectories(projectDir.toPath());
writeString(new File(projectDir, "settings.gradle"), "");
writeString(new File(projectDir, "build.gradle"), "plugins {" + " id('i.will.fail.you')" + "}");
// Run the build
GradleRunner runner = GradleRunner.create();
runner.forwardOutput();
runner.withPluginClasspath();
runner.withArguments("iwillfailyou");
runner.withProjectDir(projectDir);
BuildResult result = runner.build();
// Verify the result
Assertions.assertTrue(result.getOutput().contains("someoutput from the iwillfailyou task"));
}
private void writeString(File file, String string) throws IOException {
try (Writer writer = new FileWriter(file)) {
writer.write(string);
}
}
}

Mono.doOnError() reactor block unit test

I have a rest controller using spring webflux and reactor, I am writing unit test for the controller. Please find below the code snippets and help me to write the unit test method to test the .doOnError() block.
I have tried to throw an exception by using Mockito
doThrow(CriticalException.class)
.when(myService).myMethod(object);
This is my unit test:
StepVerifier.create(
Mono.just(
webTestClient.post()
.uri("/endpoint")
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(requestJson)) //Set the body of the request to the given synchronous Object
//Returns:a Mono with the response
//Act
.exchange() //Perform the exchange
//Assert
.expectStatus().isOk()
.expectBody(Agreement.class)
.returnResult()
.getResponseBody()))
.expectNextMatches(agreementResponse -> {
assertNotNull(agreementResponse.getAgreementParticipant());
return true;
})
.expectComplete()
.verify();
This is my controller:
return Mono.fromCallable(() -> {
myService.myMethod(object);
return object;
}).log().subscribeOn(Schedulers.elastic())
.map(p -> ResponseEntity.ok(p))
.defaultIfEmpty(ResponseEntity.notFound().build())
.doOnError(e -> {
LOGGER.error(LOG_FORMAT, e.getMessage(), e.getStackTrace());
});
Mockito is not returning exception while myService.myMethod(object) is been called.
Please suggest proper way to write test for .defaultIfEmpty() and .doOnError() blocks.
Instead of throwing CriticalException.class while mocking your myService.myMethod(object) return an exception wrapped in a Mono
For eg :
Mockito.doReturn(Mono.error(Exception::new)).when(service).callableMethod();
Find the sample code snippet below
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
class Service {
public Mono<String> callableMethod() {
return Mono.just("1");
}
}
class Controller {
private Service service;
public Controller(Service service) {
this.service = service;
}
public Mono<String> endpoint() {
return service.callableMethod().doOnError(throwable -> {
System.out.println("throwable = " + throwable);
});
}
}
public class TestClass {
#Mock
private Service service = Mockito.mock(Service.class);
#Test
public void controllerTest() {
Mockito.doReturn(Mono.error(Exception::new)).when(service).callableMethod();
StepVerifier.create(new Controller(service).endpoint()).verifyError();
}
}

Mocking a http response in kotlin

Currently I am working on a hobby project with that I want to learn a bit about kotlin.
I implemented an object that makes HTTP get requests and returns the Json object from the response.
What I'm struggeling with is the mocking of the response or the http framework in my tests.
I think if the framework would provide a class, I could manage the mocking. But as it only provides functions like khttp.get(), I'm a bit confused how to mock that.
Can someone help me, please? :)
Thanks!
The HTTPClient Class:
package dao.http.HTTPClient
import khttp.get
import org.json.JSONObject
import java.net.URLDecoder
class HTTPClient {
fun getClient(): HTTPClient {
return this
}
fun httpRequestGET(url: String): JSONObject {
val r = get(url)
return r.jsonObject
}
}
And the related test Class
import dao.http.HTTPClient
import io.mockk.every
import io.mockk.spyk
import org.hamcrest.MatcherAssert.assertThat
import org.json.JSONObject
import org.junit.jupiter.api.Test
import org.hamcrest.CoreMatchers.`is` as Is
import khttp.responses.GenericResponse
class HTTPClientTest {
#Test
fun testHTTPRequestGET() {
val http_get = spyk(khttp.get( "https://somepage.com/wp-json/tsapi/v1/user/ts/isregistered/12323"))
val httpClient = HTTPClient()
var expectedAnswer: JSONObject = JSONObject("""{"uid":"1","user":"user","is_registered":"true"}""")
every { http_get } returns GenericResponse()
var url = "https://somepage.com/wp-json/tsapi/v1/user/ts/isregistered/12323"
var actualAnswer = httpClient.httpRequestGET(url)
assertThat(actualAnswer.get("user"), Is(expectedAnswer.get("user")))
}
}
you can use it like this:
#Test
fun test() {
mockkStatic("khttp.KHttp")
verify { khttp.get(any()) }
verify(exactly = 1) { khttp.get(url = "http://google.com") }
}

Grails - How do I unit test controller asynchronous promise?

Given an extended example from the Grails documentation (http://grails.org/doc/latest/guide/async.html#asyncRequests):
import static grails.async.Promises.*
class myController {
def myServiceWithLongRunningMethod
def index() {
tasks otherValue: {
// do hard work
myServiceWithLongRunningMethod.doSomething()
}
}
}
How do I create a Spock unit test for this?
import static grails.async.Promises.*
import org.grails.async.factory.*
import grails.async.*
import grails.test.mixin.TestFor
import spock.lang.Specification
#TestFor(MyController)
class MyControllerSpec extends Specification {
def mockMyServiceWithLongRunningMethod = Mock(MyServiceWithLongRunningMethod)
def setup() {
// not sure if I need this next line
// Grails docs suggests making Promises synchronous for testing
Promises.promiseFactory = new SynchronousPromiseFactory()
controller.myServiceWithLongRunningMethod = mockMyServiceWithLongRunningMethod
}
def cleanup() {
}
void "index calls myServiceWithLongRunningMethod.doSomething()"() {
when: 'index is called'
controller.index()
then: 'myServiceWithLongRunningMethod.doSomething() is called'
1 * mockMyServiceWithLongRunningMethod.doSomething()
}
}
I get the following error:
Failure: index calls myServiceWithLongRunningMethod.doSomething()(MyControllerSpec)
| groovy.lang.MissingPropertyException: No such property: tasks for class: MyLoadingController
I'm not sure what the tests should look like - it's clear that I need to do something to enable the test class to understand the tasks method provided by Promise.

How to specify classpath ordering in Gradle

I need to control the ordering of jars in the testRuntime configuration.
I must make sure that robolectric-x.x.jar comes before android.jar, or else I get the dreaded RuntimeException("Stub!").
How do I do that?
Here is my complete build.gradle for running Robolectric tests against my Android app, which uses RoboGuice:
apply plugin: 'java'
androidJar = new File(System.getenv('ANDROID_HOME'), '/platforms/android-7/android.jar')
configurations { robo }
dependencies {
robo('com.pivotallabs:robolectric:1.0-RC1')
testCompile('org.roboguice:roboguice:1.1.2')
testCompile('junit:junit:4.8.2')
testCompile project (':app')
testCompile files(androidJar)
}
sourceSets.test.compileClasspath = configurations.robo + sourceSets.test.compileClasspath
sourceSets.test.runtimeClasspath = configurations.robo + sourceSets.test.runtimeClasspath
test {
excludes = ['**/MyRobolectricTestRunner.class']
}
I had to add an exclusion for the test runner, or else Gradle will throw an exception.
MyRobolectricTestRunner.java looks like this:
package com.acme.myapp;
import java.io.File;
import org.junit.runners.model.InitializationError;
import roboguice.application.RoboApplication;
import roboguice.inject.ContextScope;
import com.google.inject.Injector;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.RobolectricTestRunner;
public class MyRobolectricTestRunner extends RobolectricTestRunner {
public MyRobolectricTestRunner(Class<?> testClass) throws InitializationError {
// Tell Robolectric where to find AndroidManifest.xml and res/
super(testClass, new File("../app"));
}
/**
* Enable injection into tests as well...
*/
#Override
public void prepareTest(Object test) {
RoboApplication myApplication = (RoboApplication) Robolectric.application;
Injector injector = myApplication.getInjector();
ContextScope contextScope = injector.getInstance(ContextScope.class);
contextScope.enter(myApplication);
injector.injectMembers(test);
}
}
And here's a sample test that illustrates injection:
package com.acme.myapp;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import roboguice.inject.InjectResource;
#RunWith(MyRobolectricTestRunner.class)
public class StringFormattingTest {
#InjectResource(R.string.info_pending_amount)
private String pendingAmountPattern;
#Test
public void testFormatInfoPendingAmount() {
String s = String.format(pendingAmountPattern, 20.0d, "EUR");
assertEquals("Only a part of your stake (20,00 EUR) was accepted", s);
}
}
This might work:
configurations { robo }
dependencies {
robo ...
testRuntime ...
}
sourceSets.test.runtimeClasspath = configurations.robo + sourceSets.test.runtimeClasspath