Dependency Injection on Kotlin/Multiplatform — Part 2 : Test and build

Romain Boisselle
Kodein Koders
Published in
5 min readJul 6, 2020
Photo by Jeff King

This article is part of a series about using dependency injection on Kotlin/Multiplatform:

In the first part we saw how to configure our Kotlin/Multiplatform project with Kodein-DI, then we have defined our architecture and our common business code. Now we need to test our code, once, before building it and using it on both mobile platforms ; iOS and Android.

Before being able to run tests upon our Kotlin code, we will need to configure our Gradle project depending on the platform we are targeting. As we are making a Kotlin/Multiplatform project, we certainly need to define test dependencies for our common code:

You can see that in Kotlin/Multiplatform project test dependencies are like normal dependencies, as they match to specifics source sets.

The power of Kotlin/Multiplatform is to allow us write code once, and then test it once, in the commonTest source set. But, along the way, you will probably need to interact with some platform specific APIs, thus you would also need to include platform specific testing dependencies, like for the JVM:

For the Native source sets we do not need to define test dependencies, as they are built-in into kotlin-test, already added in the common source set.

This article is not about what you need to test and why, but how to use the dependency injection pattern, with Kodein-DI, to test your Kotlin/Multiplatform code, before building and using your library.

If you have read the first part, you nay have seen me coming, as I intentionally used interfaces and separate Kodein-DI modules to make my code independent, swappable and testable, without having to mock or fake the whole application for every test.

Let’s take the previous example from our first article. The login module is composed of four bindings:

Where TokenRepisotry, AuthApi and Login.Presenter are interfaces, and their implementation have the following relations:

  • AuthApiImpl needs an instance of some TokenRepository
  • LoginPresenterImpl needs an instance of some AuthApi

To test this architecture we will have to import the login module, and override some of the binding to fake their behaviour, in order to test each pieces of our code, independently. Let’’s see how we can do this.

As an example, we will be testing the LoginPresenterImpl. In order to do that, we have to fake the behaviour of the TokenRepository and AuthApi, to control their flow. Here are two implementations, for both of them, one considering that login in is a success and one considering it's not. We will also add, a fake view to check the right behaviour of the Presenter.

Now, we have to declare a DI container that will be carrying our fake implementations:

This code defines a container that is meant to be used just for our tests, with the smallest scope, to avoid unpredictable behaviours.

The following line imports the loginModule from our library. We specify that the current container can override existing dependencies of this module.

Now, we just need to override the existing bindings for AuthApi and TokenRepository:

And finally, this will allow us to instantiate a new TestLoginView every time we need one:

This DI container can be use in our test class by implementing the DIAware interface. It will help us to write our test, avoiding to call the DI container every time we need it, by calling by instance() instead of by loggedInContainer.instance(). This is possible because we will be overriding the di property, from DIAware.

Swapping our fake object is now really simple, for example here is another DI container that will help us testing when the user is not logged, or when the API calls are failing:

Now that we have our multi-platform code, tested, we can confidently build our Kotlin/Multiplatform library before using it on iOS and Android.

While building a library for Android is kind of straightforward, for iOS it hasn’t always been that simple.

Remember, to configure our Kotlin/Multiplatform project we need to define special cases for iOS, depending on the SDK we are targeting, either iOS ARM64 devices or the iOS x64 simulator. Even if we are able to build a fat universal .framework , this can lead us to managing intermediate source set, that was not trivial to use, until Kotlin 1.4-M2.

Back to the point, today there is two path for building Kotlin/Multiplatform library for iOS.

In the early versions of Kotlin/Multiplatform we had to specify a Gradle task to create a .framework file, that could be added to your Xcode configuration manually. Here is the task to add in your build.gradle.kts file.

For some convenience, here we also add the dependency on packForXcode, thus the framework would be built when building our entire project with:

However, iOS developer have tools too! CocoaPods is a dependency management tool for Swift and Objective-C projects, and JetBrains has been kind enough to develop a Gradle plugin, that gives us the ability to provide our Kotlin/Multiplatform library to iOS developers, without breaking their habits. To do so, add the plugin org.jetbrains.kotlin.native.cocapods to your gradle configuration:

Then, wee need to define the cocoapods configuration like below:

Then running the following Gradle task will make the library available for cocoapods:

(we will see what is the output and how to use it in the next article).

As I said before, building our app for Android is pretty straightforward, nowadays. For this example, we will just build a .jar. Nevertheless, you still can build an .aar with Kotlin/Multiplatform by including an Android SDK dependency into the project configuration.

To use our Kotlin/Multiplatform library on Android, we need to publish it on a package repository, like Maven or JCenter. We can use the Gradle pluginmaven-publish

And then call the corresponding task:

That all for this one! Next time, we see how to use our Kotlin/Multiplatform library on iOS and Android.

This article is brought to you by Kodein Koders, a European service & training provider focused on Kotlin & Kotlin/Multiplatform.

--

--