Mock Room database for Unit tests - android

I'm trying to make some Unit tests for my business logic.
Data is read and written to Room database, so the logic depends on what's inside my database.
I can easily buildInMemoryDatabase and test all the logic, but using Instrumental tests which are slow and require a device to be connected.
I want to run Unit tests only where I replace my RoomRepository with some other implementation of Repository interface
class RoomRepository(
private val database: RoomDatabase //actual room database
): Repository {
override fun getFooByType(type: Int): Maybe<List<Item>> {
return database.fooDao()
.getFooByType(type)
.map { names ->
names.map { name -> Item(name) }
}
.subscribeOn(Schedulers.io())
}
}
Maybe there is a way to run Room sqlite on host machine?
Maybe there is another solution?

Create a wrapper class named "Repository" around the RoomDatabase and have methods to expose the Dao objects. By that way, we can easily mock the repository class like below
Open class Repository(private val roomDatabase:RoomDatabase){
open fun productsDao():ProductsDao = roomDatabase.productDao()
open fun clientsDao():ClientsDao = roomDatabase.clientsDao()
//additional repository logic here if you want
}
Now, in tests, this class can be easily mocked like
val repositoryMock = mock(Repository::class.java)
val productsDaoMock = mock(ProductsDao::class.java)
when(repositoryMock.productsDao()).thenReturn(productsDaoMock)
when(productsDaoMock.getProducts()).thenReturn(listof("ball","pen")
So, inject and use the repository class instead of RoomDatabase class in all the places of your project so that the repository and all the Dao can be easily mocked

You usually access the database through the #Dao interfaces. These can be mocked easily.
The daos are returned from abstract methods of your actual RoomDatabase, so this could be mocked easily as well.
Just instantiate your RoomRepository with the mocks and setup these properly.

Please refer to this article.
https://developer.android.com/training/data-storage/room/testing-db
It's Unit test and not slowly like UI Test as you think.
The recommended approach for testing your database implementation is writing a JUnit test that runs on an Android device. Because these tests don't require creating an activity, they should be faster to execute than your UI tests.

Related

Why Hilt isn't necessary for unit tests, but it is necessary for UI tests?

Hilt testing guide documentaton has this paragraph about Unit test
Hilt isn't necessary for unit tests, since when testing a class that uses constructor injection, you don't need to use Hilt to instantiate that class. Instead, you can directly call a class constructor by passing in fake or mock dependencies, just as you would if the constructor weren't annotated:
#ActivityScoped
class AnalyticsAdapter #Inject constructor(
private val service: AnalyticsService
) { ... }
class AnalyticsAdapterTest {
#Test
fun `Happy path`() {
// You don't need Hilt to create an instance of AnalyticsAdapter.
// You can pass a fake or mock AnalyticsService.
val adapter = AnalyticsAdapter(fakeAnalyticsService)
assertEquals(...)
}
}
But here you can see that documentation is explaining how to use Hilt in UI test.
My question is why Hilt isn't necessary for unit tests, but it is necessary for UI tests?
With unit tests you verify behavior inside the classes, while in UI test you verify UI state given data. In unit test you don't need Hilt to generate a object tree, you are testing a small building unit of your app, a class, a function. Your unit test have limited scope of objects needed, so that's another reason why you don't need Hilt to build entire tree of objects each unit test.
In unit test you verify that an event have happened, a function has been called or a result has been returned given an input.
Hilt is injecting fake components in your UI test through which you are providing data that is rendered.

Why do we need to use DAO and Repository in the same time on Android Project?

I am working on a path project to learn Android deeply but I am really confuse about implemantation of dao and repository. What is the difference between dao and repository on android. Is repository a common pattern ?
https://github.com/basaransuleyman/KetgoMVVM
Room is a layer over the SQLite database.
DAO forms the main component of the Room Persistence Library. We use queries to perform CRUD operations on the database(Insert,Update,Delete and Create). And DAO is the place, where we add such queries to perform operations. Inside a DAO we define methods.
On the other hand, a repository class is something that abstracts access to multiple databases. Although it is not part of the Architecture Component libraries, but is used as a best practice.
Example: Let say there is a Person Database , with a single table Person(known as entity).
For this example, we want to perform the following operations on the Person table:
Insertion
Update
Read
For all the operations, we need to write queries. Those queries are meant to be written under a DAO class, here PersonDao
#Dao
interface PersonDao {
#Insert
suspend fun insertPerson(person1 : PersonModel)
#Update
suspend fun updatePerson(persone1 : PersonModel)
#Query(SELECT * FROM person)
fun getAllPerson() : LivaData<List<PersonModel>>
}
Here, PersonModel is a model class to store the Person entity.
Next time, if we need to get the access to the Person table, we will implement the methods of the PersonDao. For example, like this:
private val personDao : PersonDao
private val personList : LiveData<List<PersonModel>>
init{
personList = personDao.getAllPerson()
}
But wait, what if we have multiple database, we have to create a lot of DAO objects, which would make the code redundant, and prone to errors. So as to avoid that, we define a Repository.
class PersonRepository (private val personDao : PersonDao){
suspend fun insertPerson(person1 : PersonModel) =
personDao.insertPerson(person1)
suspend fun updatePerson(person1 : PersonModel) =
personDao.updatePerson(person1)
fun getAllPerson() : LiveData<List<PersonModel>> = personDao.getAllPerson()
}
Repository is a common pattern. See https://www.martinfowler.com/eaaCatalog/repository.html. A Data Access Object (DAO) defines the interface for how to perform CRUD operations on a particular entity. A Repository can have several DAOs.
See also https://developer.android.com/training/data-storage/room/accessing-data

Dao in Usecase. MVVM or Clean Architecture anti-pattern?

In our "SearchUsecase" we have access to "ShowFtsDao" directly.
Does it violate the Clean Architecture principles? Does it violate the MVVM architecture?
Assuming our intention is to develop a well-built, standard structure, is there anything wrong with this piece of code?
class SearchUsecase #Inject constructor(
private val searchRepository: SearchRepository,
private val showFtsDao: ShowFtsDao,
private val dispatchers: AppCoroutineDispatchers
) : SuspendingWorkInteractor<SearchShows.Params, List<ShowDetailed>>() {
override suspend fun doWork(params: Params): List<ShowDetailed> {
return withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
if (remoteResults.isNotEmpty()) {
remoteResults
} else {
when {
params.query.isNotBlank() -> showFtsDao.search("*$params.query*")
else -> emptyList()
}
}
}
}
data class Params(val query: String)
}
I believe your use case handles more logic than it needs to.
As a simple explanation I like to think about the components this way:
Sources: RemoteSource (networking), LocalSource (db), optionally MemorySource are an abstraction over your database and networking api and they do the IO thread switching & data mapping (which comes in handy on big projects, where the backend is not exactly mobile driven)
Repository: communicates with the sources, he is responsible for deciding where do you get the data from. I believe in your case if the RemoteSource returns empty data, then you get it from the LocalSource. (you can expose of course different methods like get() or fetch(), where the consumer specifies if it wants the latest data and based on that the repository calls the correct Source.
UseCases: Talk with multiple repositories and combine their data.
Yes, it does.
Because your domain module has access to the data module and actually you've violated the dependency rule.
That rule specifies that something declared in an outer circle must
not be mentioned in the code by an inner circle.
Domain layer must contain interfaces for details (Repositories) which are implemented in the data layer,
and then, they could be injected into the UseCases (DIP).

Non Instrumentation Tests for Room Database

I'm writing tests (not instrumentation tests) for the service layer. I'd like to use the actual DAO layer instead of mocks as this makes service layer tests more functional (IMHO). I know how to create an in-memory room db for an instrumentation test:
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
However, this won't work in the other tests as the call to getApplicationContext makes a call to the InstrumentationRegistry which won't work in non-instrumentation tests. I realize that the room tests should be instrumentation tests; they are. All DAO tests are instrumentation tests which aim to test out the queries which have been written. I also realize that these aren't technically unit tests; I'm okay with that. In my experience, service layer tests which do not mock the repository layer are less brittle than those which do. Anyway, my question is - how can I accomplish this goal? Is there a way to retrieve the application context without instrumentation? Is there a room db stand-in that doesn't require the application context? Or do I need to implement another version of the DAO classes for the tests?
robolectric will allow you to run this kind of test.
#RunWith(RobolectricTestRunner::class)
#Config(sdk = [27])
class Db {
private lateinit var db: AppDatabase
private lateinit var myDao: myDaoType
#Before
fun createDB() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.fallbackToDestructiveMigration().allowMainThreadQueries().build()
myDao = db.myDao()
}
#After
fun closeDb() {
db.close()
}
}

Android plain Junit with Dagger 2

I used to work in MVP and I usually test my presenters using a plain Junit (Not the Instrumentation !) , since Presenters only have the business logic and no references to Android internals whatsoever.
Now by switching to Dagger 2 , I understood that I have a problem setting up a "TestModule" for my app component.
Creating a component will not work from within a test class (probably because "apt" is not running there)
Didn't find any examples for using Dagger with a standard Junit testing. Every example I have found only relies on Instrumentation testing or Roboelectric (which basically mocks Activities and other Android related stuff) , but this is just a UI testing for me , and I don't need that.
Just to make things clear , I am talking about the tests that are located at app->src->test folder not the app->src->androidTest !
So do I do something wrong ? Or missing something ? Can anyone explain or give examples on how to use Dagger 2 in normal unit tests ?
I'm not sure if my solution will work for you but I see no reason it shouldn't.
First I created testInjectionComponent
#Singleton
#Component(modules = {MockNetworkModule.class})
public interface MockInjectionComponent extends InjectionComponent {
void inject(DaggerUnitTest daggerUnitTest);
}
Then my Unit Tests I add injection in the before method. like so:
#Before
public void setUp() throws Exception {
MockInjectionComponent mockInjectionComponent = DaggerMockInjectionComponent
.builder()
.mockNetworkModule(new MockNetworkModule())
.build();
mockInjectionComponent.inject(this);
}
Then I just Annotate my Injected Object.
EDIT :
Do not forget to add testApt "com.google.dagger:dagger-compiler:$daggerVersion" at your app.gradle file .
As mentioned by the accepted answer. Do not forget to add :
For Java
Android Test
androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:$dagger_version'
JUnit test
testAnnotationProcessor 'com.google.dagger:dagger-compiler:$dagger_version'
For Kotlin
Android Test
kaptAndroidTest 'com.google.dagger:dagger-compiler:$dagger_version'
JUnit test
kaptTest 'com.google.dagger:dagger-compiler:$dagger_version'
You don't need any dagger to test your presenter. Dagger's job is it to fullfill the dependencies of your classes (dependency injection).
For example you have this Presenter:
public class MyPresenter {
Database database;
ApiService apiService;
#Inject
public MyPresenter(final Database database, final ApiService apiService) {
this.database = database;
this.apiService = apiService;
}
}
Dagger will provide your Presenter with the database and apiService objects for your presenter to use them. When running the actual app (not a test) these will be real objects with real functionality.
When testing the presenter, you want to test only the presenter, everything else should be mocked.
So when you create the presenter in your PresenterTest, you create it with mocked versions of database and apiService.
You can then test how your presenter interacts with these object by
a. mocking the objects behaviour like
when(database.getSomething()).thenReturn(something)
b. verify your presenter does what you want it to do with these objects like
verify(database).saveSomething()
(pseudo code)
Standard way to mock would be Mockito.
You can swap out real modules with fake modules in two ways: do it at compile time using flavors as recommended by google architecture samples or at runtime by creating an abstract method which injects the real thing in production code and fake dependencies in test code. In the second case your test has to subclass the class that you want to mock and build the component from scrach

Categories

Resources