Let’s talk about Unit Test in Keddit App. I’m not going to mention the reasons why we need to create unit tests, we all already know that it’s a critical part of any app development to have a set of unit tests. So today we are going to add some unit tests to this App, those will be focused on the NewsManager class, the one responsible to request, map the Reddit news and key place to be the principal cause of all the mayors problems regarding internet connectivity.
NewsAPI Interface
Just to make unit test easier, I created a new interface for the News API so now I just can pass a mock of this interface. There is nothing crazy with this new interface so let’s jump to the next part.
NewsManager Unit Tests
We need to configure the project and create our unit tests for the NewsManager.
Mockito Dependency
Mockito is an incredible lib to make unit tests really awesome and you can use it in Kotlin!
testCompile “org.mockito:mockito-core:1.+”
But! It’s not all a bed of roses…
If you try to mock a class that needs a generics definition, like trying to mock the Call<T> class from Retrofit, it will not work. But you can still make it work with a useful Extension Function:
inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
Remember that we saw the “reified” keyword in the previous chapter.
Now this will allow us to do something like this:
var callMock = mock<Call<RedditNewsResponse>>()
And we are ready to go!
Testing Observables with TestSubscriber
A simple way to test your observables is by using the TestSubscriber class provided by RxJava. It will allows you to run an Observable and make some assertions:
var testSub = TestSubscriber<RedditNews>() newsManager.getNews("").subscribe(testSub) testSub.assertNoErrors() testSub.assertValueCount(1) testSub.assertCompleted()
Commit: Unit Tests
A complete example of one unit test is like this:
@Test fun testSuccess_basic() { // prepare val redditNewsResponse = RedditNewsResponse(RedditDataResponse(listOf(), null, null)) val response = Response.success(redditNewsResponse) `when`(callMock.execute()).thenReturn(response) // call val newsManager = NewsManager(apiMock) newsManager.getNews("").subscribe(testSub) // assert testSub.assertNoErrors() testSub.assertValueCount(1) testSub.assertCompleted() }
Note: There are some required initialization steps that can be reviewed in the commit.
Preview in Android Studio
Spek Framework
Spek is a Specification Framework for the JVM created by JetBrains
This framework allows you to describe tests and expected behaviours in a more readable way, like this:
class TaxCalculatorSpecs: Spek({ given("Tax rate calculator with default locale settings") { val taxRateCalculator = TaxRateCalculator() on("calculating the rate for an income of 200...") { val value = taxRateCalculator.calculateRate(200, 10) it("should result in a value of 300") { assertEquals(300, value) } } } })
Here you have some sample code from the official Spek github repo to get some inspiration.
Re-implementing Tests with Spek
Spek version used: 1.0.25
I have to say that I learned a lot about Spek trying to use it in the Keddit App because at the beginning I thought that I could use it in this way:
given("a NewsManager") { // 1. setup code here beforeEach { // 2. clean and setup for each "on" section } on("service returns something") { // 3. execute code with specific parameters for this section it("should receive something") { // 4. asserts } } on("another mocked service call") { // so on... } }
For my surprise, my first implementing didn’t work as the given steps were not executed in “my expected logical order”. The execution was in a different way:
given("a NewsManager") { // 1) beforeEach { // 4) before every 'it' } on("service returns something") { // 2) beforeEach { // 5) before every 'it' in this 'on' section } it("should receive something") { // 6) } } on("another mocked service call") { // 3) ... } }
The code is executed in the provided steps, the numbers will be indicating the execution order.
Execution order and suggested usage:
1) Code specific for the “given” body. Here you can declare global variables for all your unit tests.
2) and 3) Spek will execute all your “on” sections. So this is not the best place to initialize your sections. If you do this, you will end up like me having the issue that the last section will be overriding all your previous section setup. In order to setup your section you will need to use the “beforeEach” to do this. Here you can declare local variables to be used only in this section.
4) This beforeEach is related to every “it” section. This will be executed before EVERY “it” section is executed.
5) In a similar way to the step 4, this beforeEach will be executed before every “it” section but inside the current “on” section. Here you can use this section to initialize your tests. For me this is not the best place to do this as maybe you don’t want to execute the same piece of code for each “it” section but I didn’t find another way.
6) Execute the “it” section. Place for asserts.
Preview in Android Studio
Another great feature about Spek is that you can preview the executed test in a more readable way. Here you have both Tests executed, the NewsManagerTest (you only see the method names) and the NewsManagerSpekTest (using Spek framework):
As you can see, NewsManagerSpekTest class gives you more details about every test, printing the “given, on and it” sections.
Spek Preview issue
Right now there is an issue in Spek that it’s not adding the words “given” and “on” in the test preview. I sent a PR to make it behave like previous versions of Spek.
https://github.com/JetBrains/spek/pull/90
With the fix, the preview looks like this:
Run Tests individually
At the beginning I thought that it was not possible to run an individual test as the IDE doesn’t show you the regular green arrow next to the test to run but seems that the guys behind Spek thought about this and they provided an special syntax to accomplish this. Add as a prefix the letter “f” in any section and you will be able to do “focus” on this special section, like this:
fit("should receive something and no errors") { ... }
And run all test in the class:
Ignore Test
Also you can ignore a test with a prefix of “x” like this:
xit("should receive something and no errors") { ... }
This is really useful to start describing the expected behaviour for your classes but you don’t have it yet implemented.
Commit: Adding Spek and migrating tests
And here you have the commit with all the code added to use Spek in Keddit:
https://github.com/juanchosaravia/KedditBySteps/commit/479687deceddd892fb03a904b2e5dfd5fab21094
Conclusion
Kotlin is getting more and more exiting as time passes!
Please feel free to contribute in this section with a comment or a PR as I’m still learning about the Spek framework and for sure there are better ways to accomplish the same thing.
See you soon in another article! 🙂
Share your thoughts