Mock factory for Android testing in multi-module system

Marius Merkevičius
Treatwell Engineering
6 min readJun 3, 2018

--

Very weird and long title. Let me explain…

TL;DR

This post explores how to export object creation factory that could be reused through all over the modules in your Android project. To find out, just scroll down to the #solution. The whole story is a bit below. Also, here is the example project on github.

Baby steps

Here at Treatwell we have many products that solve different problems for our beloved salons and through out the time, we’ve managed to achieve some cool things. Personally, I work on our product Treatwell Connect for Android platform, that its sole responsibility is to be the best management software for the salon. Through this time we’ve managed to work out stuff that I’ve felt like I would love to share it with the world.

So here it goes.

Problem

When we do any kind of work on Connect, we try to follow the first scout rule — leave the playground better than it was before. In that sense, to achieve this I mostly lean on unit testing. Also, our app is not that small, so it is split into different modules instead of having one big bundle. By doing this, beside getting more compilation speed, more rapid feedback and happier developers, this also creates some minor inconveniences.

Let me elaborate.

So this is how our app is split (we have even more components, but the main ones are these):
- model contains all the entity models for all the app and its parts
- components logical components that have little to do with android framework. Mostly business logic, loaders, display components, utils, formatters etc.
- ui exported views (did i mention we have quite a bit of complex views?) and their bindings
- app the whole application that connects (sorry, no pun intended:( ) everything

So as you know a bit more about background, here is the problem. One of the most common things that you do when you are writing unit tests — you create mocks (or face classes with fake data if you please). The problem when having this multi-module environment is, if you create a mock utility class for easier class creation, it cannot be reused in another modules. Its because a) you only would like to have this kind of class(es) only in test environment and b) test environments do not share classes between different modules.

Example

Yep, and here is an example.

So for instance we have this cool app, that we try to assemble. We have:
- app main app module, every library module connects to this.
- components logic thingies goes here, uses models
- models entities

To assemble everything, we need to connect it. So in build.gradle of the components module, we insert association to models module:

dependencies {

implementation project(‘:models’)

}

And on the app module, we inject every module we will be using.

dependencies {

implementation project(‘:components’)
implementation project(‘:models’)

}

Now we have a multi module project. Lets create some entities we will be using in models module. We will use Session that packs UserSettings.

class Session(
val id: Long,
val userSettings: UserSettings
)
data class UserSettings(
val id: Long,
val name: String,
val age: Int
) {
fun ageDoubler(age: Int): Int {
return age + age
}
}

As you can see, our UserSettings has a method also that does some *serious calculations* (not really), so we need to unit test it. As the class is quite *big* (again, I’m joking, its only for demonstration) to create every time we want to use it, we’ll create Mocks class that will let us easier generate these entity models and put it in test scope (it would not be cool to have this class in production app, would it?).

object Mocks {

fun createUserSettings(
id: Long = 1L,
name: String = "valid_name",
age: Int = 10
): UserSettings {
return UserSettings(
id = id,
name = name,
age = age
)
}

fun createSession(
id: Long = 1L,
userSettings: UserSettings = createUserSettings()
): Session {
return Session(
id = id,
userSettings = userSettings
)
}

}

There we go, it even has default entity values. Its very easy whenever you may need any kind of models like these. So we move on to some unit testing now.

class UserSettingsTest {

@Test
fun valid() {
// Assemble
val userSettings = Mocks.createUserSettings(
id = 2L,
name = "mark",
age = 21
)
val session = Mocks.createSession(
id = 1L,
userSettings = userSettings
)

// Act
val resultAge = userSettings.ageDoubler(10)

// Assert
assertEquals(20, resultAge)
}

}

We use our Mocks to create the models, and test some kind of behaviour.
So till now, everything is fine and its very easy to do some testing. Now lets move on to components module.

We’ll use the same entity models we defined before and additional class to extract some data.

class CoolComponent() {
fun extractUserName(session: Session): String {
return session.userSettings.name
}
}

And we move on to do some unit testing for this method.

class CoolComponentTest {
@Test
fun valid() {
// Assemble
val coolComponent = CoolComponent()
val session = Mocks.createSession(
userSettings = Mocks.createUserSettings(
id = 2L,
name = "cool_name",
age = 16
)
)

// Act
val resultName = coolComponent.extractUserName(session)

// Assert
assertEquals("cool_name", resultName)
}
}

So, IDE does not have any problem using any of these things. We see Session, we see UserSettings and even Mocks class. But the problems start whenever you try to run things.

e: …/components/CoolComponentTest.kt: (3, 21): Unresolved reference: Mocks

When running tests in components module that reference Mocks that is in models module test scope, this breaks bad :(

Try writing passing test when the code does not even compile

Solution

So to solve this,

  • We will be creating another module — mock_factory that holds only classes which holds helper factory methods to create objects
  • We move mock factory class Mocks from models module test folder to mock-factory module main folder. That means this class can be seen in all modules that are using this
  • We define that our mock-factory module will be using models module as a dependency (we will need the scope of the objects that we will be creating). To do this, we update our build.gradle for the mock-factory module:
dependencies {

implementation project(‘:models’)

}
  • We update our modules that will be using Mocks creation factory, so we update our build.gradle to our models and components modules. The only difference is we will be injecting class with test scope.
dependencies {

testImplementation project(‘:mock-factory’)

}
  • We clean and rebuild the project…
  • ..and run the tests for models and components modules. All the tests are running fine and we have central repository for easier mock creation factory. Yay!
Passing tests in `components`
Passing tests in `models`

So before solving this, I’ve had a couple of different approaches for this. Some included code duplication, some .jar generation and other tricks. Whenever I’ve found such an elegant solution, I knew I have to share it. So hope this will help out anyone and will make the unit tests more happy.

--

--