Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Contributor needed] UI testing with ViewModels #48

Closed
dGorod opened this issue Feb 10, 2018 · 10 comments
Closed

[Contributor needed] UI testing with ViewModels #48

dGorod opened this issue Feb 10, 2018 · 10 comments

Comments

@dGorod
Copy link

dGorod commented Feb 10, 2018

Hello, @arnaudgiuliani !

Thank you for a great DI library. Starting using it and need some help with testing.

I have Android project with architecture components. I inject ViewModel using lazy 'by viewModel()' method. Inside ViewModel I have a repository that goes to API. Now I want to write UI tests for my app using Espresso. Obviously, I don't need real calls to the Internet, I want mocked data returned using Mockito.
And now I'm confused in what direction I should go. I read article about testing in documentation. But still can't figure out how to inject mocked repository inside my ViewModel for Espresso test.

Will appreciate any help.
Thanks.

@dGorod
Copy link
Author

dGorod commented Feb 10, 2018

Test project where want to try it first: https://github.com/dGorod/test_500px_api_kotlin

@arnaudgiuliani
Copy link
Member

Hello,

this is one the hot topics for Koin. Anybody with Espresso knowledge is welcome to give help for Koin 👍

The article you mention deals about unit testing (testing before the UI) - Which is (in my point of view) essential to do before making any UI test.

I'll put that on top priority.

@arnaudgiuliani arnaudgiuliani changed the title UI testing with ViewModels [Contributor needed] UI testing with ViewModels Feb 12, 2018
@mataiaslev
Copy link

mataiaslev commented Feb 12, 2018

Hi,

For insert mock objects under Espresso I am essentially configuring the tests for use another "Application" class for start. That use another startKoin(..) method, that calls our TestModules in place of the modules for the Real app. (This imply make some methods open, and probably will be improved.)

Configurations and Code:

Gradle:

defaultConfig {
        testInstrumentationRunner 'com.your.path.CustomInstrumentationTestRunner'
        ...
}

and I add the library androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.12.1'
for let mockito do his job in this context.

Custom AndroidJUnitRunner under the folder of the espresso tests:

class CustomInstrumentationTestRunner: AndroidJUnitRunner() {

    @Throws(InstantiationException::class, IllegalAccessException::class, ClassNotFoundException::class)
    override fun newApplication(cl: ClassLoader,
                                className: String,
                                context: Context): Application {
        return Instrumentation.newApplication(MyIntegrationTestApp::class.java, context)
    }

}

Our application for tests:

class MyIntegrationTestApp: MainApplication() {
    override fun onCreate() {
        super.onCreate()
        val testModule = TestModule()
        val authenticator = Mockito.mock(Authenticator::class.java)
        Mockito.`when`(authenticator.isAuthenticated()).thenReturn(true)
        testModule.authenticator = authenticator
        startKoin(this, listOf(testModule))
    }
}

My TestModule looks like:

class TestModule: AndroidModule() {
    lateinit var authenticator: Authenticator

    override fun context() = applicationContext {
        context(name = "MainActivity") {
            provide { context }
            provide { authenticator }
            provide { AuthUIWrapper() }
            provide { ActionIntentGenerator(get(), get()) }
            provide { AuthViewModel(get(), get()) }
            provide { FirebaseAuth.getInstance() }
            provide { Screen(get()) }
            provide { PetFactory(get()) }
            provide { MutableLiveData<List<Pet>>() }
            provide { PetHelper() }
            provide { PetAdapter() }
            provide { FirebaseFirestore.getInstance() }
            provide { PetViewModel(get(), get(), get()) }
            provide { FirestorePetRepository(get(), get()) } bind PetRepository::class
        }

    }
}

So, I can set the Authenticator with the mock I want for the Espresso tests.

# RELATED TO THE UNIT TESTS:

What I do is create a TestModule: (Extends for Module and not for AndroidModule in this context) and startKoin into the tests with this modules.

class MyTestModule: Module() {

    lateinit var petRepository: PetRepository
    lateinit var petFactory: PetFactory
    lateinit var petHelper: PetHelper

    override fun context(): Context = applicationContext {
        provide { petRepository }
        provide { petFactory }
        provide { petHelper }
    }
}

Test example: we are using startKoin on koin context, just need the list of modules.

"PetViewModel should fetch data from the pet repository, when solicited data" {
            val testData: LiveData<List<Pet>> = MutableLiveData()
            val repository = Mockito.mock(PetRepository::class.java)
            val petFactory = Mockito.mock(PetFactory::class.java)

            val myTestModule = MyTestModule()
            myTestModule.petRepository = repository
            myTestModule.petFactory = petFactory

            startKoin(listOf(myTestModule))

            val petViewModel = PetViewModel()
            `when`(repository.getAllPets()).thenReturn(testData)

            val data = petViewModel.getAllPets()

            verify(repository).getAllPets()
            data shouldBe testData
        }

I am using kotlintest. And I can insert mocks :)

Thanks for all, And tell me please if something is not clear. I have experience in development but I am very new sharing my knowledge hahaha so Just tell me and I can improve :)

@arnaudgiuliani
Copy link
Member

Interesting 👍

@cyberratt
Copy link

cyberratt commented Mar 11, 2018

You could just change your myTestModule, for instance instead of assigning a mock to the petRepository, just changes the provide method to a 'mock(PetRepository::class.java) as PetRepository' or if you're using mockito-kotlin just mock<PetRepository>()

@arnaudgiuliani
Copy link
Member

In fact, Koin is already compatible with Android instrumented espresso tests. Just need precise explanation on what and how to do things

@arnaudgiuliani
Copy link
Member

Instrumented test are compatible as is with Koin. I will document it

@hoombar
Copy link

hoombar commented Aug 9, 2018

I couldn't find any documentation around Espresso testing or any samples - are these in the works?

@arnaudgiuliani
Copy link
Member

You can find espresso/room tests in https://github.com/InsertKoinIO/koin/tree/1.0.0/koin-projects/examples/android-mvvm/src/androidTest/java/fr/ekito/myweatherapp

Just use the KoinTest interface to tag your test class and use Koin features. The espresso test will use the default Koin configuration from your application. You can then override definitions to help you make your tests.

A good article https://proandroiddev.com/testing-with-koin-ade8a46eb4d

@cynw
Copy link

cynw commented Dec 14, 2018

In addition to @mataiaslev answer, my project is using MockMaker mockito extension to run unit test.
I got this compile error

More than one file was found with OS independent path 'mockito-extensions/org.mockito.plugins.MockMaker'

To fix this error, add exclude group: 'org.mockito' to the dependency in the gradle file.

androidTestImplementation("org.koin:koin-test:1.0.2") { exclude group: 'org.mockito' }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants