Reputation: 736
I'm new to testing and I adapted Koin as my dependency injection. my application is working fine. it's still having a login function. Here's my dependency class
Modules.kt
val applicationModule = module (override = true) {
single { NetworkService.getInstance().getService(APIService::class.java) }
single { PreferenceManager.getDefaultSharedPreferences(androidContext()) }
}
val activityModule = module {
scope(named<LoginActivity>()) {
scoped { (activity: LoginActivity) ->
Navigation
.findNavController(activity, R.id.hostFragment)
}
}
scope(named<MainNavigationActivity>()) {
scoped { (activity: MainNavigationActivity) ->
Navigation
.findNavController(activity, R.id.hostFragment)
}
}
}
val viewModelModule = module {
viewModel { LoginViewModel(loginRepository = get()) }
}
val repositoryModule = module (override = true) {
single { LoginRepository() }
}
I'm trying to write a simple unit test for the login function in my LoginRepository
. I MockWebServer
to moke the response and get the result. here's the code in LoginRepository
LoginRepository
class LoginRepository: KoinComponent {
val network: APIService by inject()
var loginMutableData = MutableLiveData<SingleLiveEvent<Resource<UserSession>>>()
fun getLoginStatus(): LiveData<SingleLiveEvent<Resource<UserSession>>> {
return loginMutableData
}
fun generalLogin(email: String, encryptedPassword: String){
val login = network.login(email, encryptedPassword)
login.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if(response.isSuccessful){
if(response.body()?.status == 1){
val resource = Resource<UserSession>(true,"Success")
response.body().let {
if(it?.session != null){
resource.data = UserSession(it.session.userId!!,it.session.fullName!!)
}
}
loginMutableData.value = SingleLiveEvent(resource)
}else{
val resource = Resource<UserSession>(false,response.body()?.msg ?: "Login failed. Try again")
loginMutableData.value = SingleLiveEvent(resource)
}
}else{
loginMutableData.value = SingleLiveEvent(Resource(false, response.message()))
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
loginMutableData.value = SingleLiveEvent(Resource(false, t.localizedMessage))
}
})
}
}
so I wrote below test class to test this generalLogin(email: String, encryptedPassword: String)
function in LoginRepository
.
LoginRepositoryTest
class LoginRepositoryTest : KoinTest {
private val loginRepository: LoginRepository by inject()
private val server by lazy { MockWebServer() }
private lateinit var network: APIService
@get:Rule
val rule = InstantTaskExecutorRule()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
startKoin {
printLogger()
modules(repositoryModule)
}
}
@Test
fun testLoginWithCorrectCredentials() {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("{\"msg\":\"success\",\"status\":1,\"session\":{\"userName\":\"Chathuran\",\"loggedin_user_email\":\"[email protected]\"}}")
)
server.start()
val testingUrl = server.url("account/userAuth/api_login/")
network = NetworkService.getInstance().getService(APIService::class.java, testingUrl)
loadKoinModules(module{network})
val email = "[email protected]"
val password = "valid_password"
loginRepository.generalLogin(email, password)
loginRepository.getLoginStatus().observeForever {
it.getContentIfNotHandled()?.also { resource ->
Assert.assertEquals(email, resource.data?.email)
}
}
}
@After
fun tearDown() {
stopKoin()
server.shutdown()
}
}
So in this class, I tried to override the APIService
instance with the one I created in test calls. This way I can tell my Retrofit
instance to use the base URL provided by MockWebServer
. But I'm getting the following error from Koin
.
org.koin.core.error.NoBeanDefFoundException: No definition found for 'com.findmyfare.mobile.app.network.APIService' has been found. Check your module definitions.
at org.koin.core.scope.Scope.findDefinition(Scope.kt:170)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:164)
at org.koin.core.scope.Scope.get(Scope.kt:128)
at com.findmyfare.mobile.app.repository.LoginRepository$$special$$inlined$inject$1.invoke(Scope.kt:327)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at com.findmyfare.mobile.app.repository.LoginRepository.getNetwork(LoginRepository.kt)
at com.findmyfare.mobile.app.repository.LoginRepository.generalLogin(LoginRepository.kt:29)
at com.findmyfare.mobile.app.repository.LoginRepositoryTest.testLoginWithCorrectCredentials(LoginRepositoryTest.kt:56)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
If this is wrong what is the correct way to override APIService
instance? Thanks.
Edit: my build.gradle dependencies
// Koin
def koin_version = '2.0.1'
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
testImplementation "org.koin:koin-test:$koin_version"
//Testing
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.21.0"
testImplementation 'android.arch.core:core-testing:1.1.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.2.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
Upvotes: 5
Views: 11284
Reputation: 39873
You're actually not overriding a bean with your module
network = NetworkService.getInstance().getService(APIService::class.java, testingUrl)
loadKoinModules(module{network})
Instead declare the network bean as an override
network = NetworkService.getInstance().getService(APIService::class.java, testingUrl)
loadKoinModules(module { single(override=true) { network } })
In addition you don't need to use override
with your main modules.
With your implementation you don't even need an override
with the above. You're not starting the main module with Koin before the test. This might result in another issue, so make sure to have all modules you need running.
startKoin {
printLogger()
modules(applicationModule, repositoryModule)
}
Upvotes: 7