# 1. Starting of testing In this testing instruction set you will learn how to write simple tests using mockito. Every test will be around one class and all of it's dependencies will be mocked out. Also suspend functions will be tested so you will see how to do that as well. I would suggest to open this document in your browser, while working in Android Studio. ## Our First Class Test with basic mocking - First let's check out the class we will test: ```kotlin org.fnives.test.showcase.core.session.SessionExpirationAdapter ``` As you can see it's a simple adapter between an interface and it's received parameter. - Now navigate to the test class: ```kotlin org.fnives.test.showcase.core.session.CodeKataFirstSessionExpirationAdapterTest ``` ### 1. Setup As you can see the test is empty, so let's declare our System Under Testing (`sut`) and our mocked dependency: ```kotlin private lateinit var sut: SessionExpirationAdapter // System Under Testing private lateinit var mockSessionExpirationListener: SessionExpirationListener ``` Now we need to initialize it, create a method names `setUp` and annotate it with `@BeforeEach` and initialize the `sut` variable, we will see that the adapter expects a constructor argument ```kotlin @BeforeEach // this means this method will be invoked before each test in this class fun setUp() { mockSessionExpirationListener = mock() // this creates a mock instance of the interface sut = SessionExpirationAdapter(mockSessionExpirationListener) } ``` Great, now what is that mock? Simply put, it's a empty implementation of the interface. We can manipulate that mock object to return what we want and verify it's method calls. ### 2. First simple test So now you need to write your first test. When testing, first you should start with the simplest test, so let's just do that. When the class is created, the delegate should not yet be touched, so create a test for that: ```kotlin @DisplayName("WHEN nothing is changed THEN delegate is not touched") // this will show up when running our tests and is a great way to document what we are testing @Test // this defines that this method is a test, needs to be org.junit.jupiter.api.Test fun verifyNoInteractionsIfNoInvocations() { verifyZeroInteractions(mockSessionExpirationListener) // we verify that our mock object's functions / properties have not been touched } ``` Now let's run out Test, to do this: - on project overview right click on FirstSessionExpirationAdapterTest - click run - => At this point we should see Tests passed: 1 of 1 test. ### 3. Test verifying actual method call Now let's add an actual method test, we will call the `onSessionExpired` and verify that the delegate is called exactly once: ```kotlin @DisplayName("WHEN onSessionExpired is called THEN delegated is also called") @Test fun verifyOnSessionExpirationIsDelegated() { sut.onSessionExpired() // the action we do on our sut // verifications verify(mockSessionExpirationListener, times(1)).onSessionExpired() // onSessionExpired was called exactly once verifyNoMoreInteractions(mockSessionExpirationListener) // there were no more additional touches to this mock object } ``` Now let's run our tests with coverage: to do this: - right click on the file - click "Run with coverage". - => We can see the SessionExpirationAdapter is fully covered. If we did everything right, our test should be identical to SessionExpirationAdapterTest. ## Second Class test with suspend functions and mocking Our System Under Test will be `org.fnives.test.showcase.core.login.LoginUseCase`. What it does is: - verifies parameters, - if they are invalid it returns an Error Answer with the error - if valid then calls the remote source - if that's successful it saves the received data and returns Success Answer - if the request fails Error Answer is returned Now this is a bit more complicated, let's open our test file: ```kotlin org.fnives.test.showcase.core.login.CodeKataSecondLoginUseCaseTest ``` - declare the `sut` variable and it's dependencies, you should be familiar how to do this by now. ### 1. `emptyUserNameReturnsLoginStatusError` now let's write our first test: `emptyUserNameReturnsLoginStatusError` first we declare what kind of result we expect: ```kotlin val expected = Answer.Success(LoginStatus.INVALID_USERNAME) ``` next we do the actual invokation: ```kotlin val actual = sut.invoke(LoginCredentials("", "a")) ``` lastly we add verification: ```kotlin Assertions.assertEquals(expected, actual) // assert the result is what we expected verifyZeroInteractions(mockLoginRemoteSource) // assert no request was called verifyZeroInteractions(mockUserDataLocalStorage) // assert we didn't modify our storage ``` But something is wrong, the invoke method cannot be executed since it's a suspending function. To test coroutines we will use `runBlockingTest`, this creates a blocking coroutine for us to test suspend functions, together it will look like: ```kotlin @DisplayName("GIVEN empty username WHEN trying to login THEN invalid username is returned") @Test fun emptyUserNameReturnsLoginStatusError() = runBlockingTest { val expected = Answer.Success(LoginStatus.INVALID_USERNAME) val actual = sut.invoke(LoginCredentials("", "a")) Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockLoginRemoteSource) verifyZeroInteractions(mockUserDataLocalStorage) } ``` `Assertions.assertEquals` throws an exception if the `expected` is not equal to the `actual` value. The first parameter is the expected in all assertion methods. ### 2. `emptyPasswordNameReturnsLoginStatusError` Next do the same thing for `emptyPasswordNameReturnsLoginStatusError` This is really similar, so try to write it on your own, but for progress the code is here: ```kotlin @DisplayName("GIVEN empty password WHEN trying to login THEN invalid password is returned") @Test fun emptyPasswordNameReturnsLoginStatusError() = runBlockingTest { val expected = Answer.Success(LoginStatus.INVALID_PASSWORD) val actual = sut.invoke(LoginCredentials("a", "")) Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockLoginRemoteSource) verifyZeroInteractions(mockUserDataLocalStorage) } ``` You may think that's bad to duplicate code in such a way, but you need to remember in testing it's not as important to not duplicate code. Also we have the possibility to reduce this duplication, we will touch this in the app module test. ### 3. `invalidLoginResponseReturnInvalidCredentials` Let's continue with `invalidLoginResponseReturnInvalidCredentials` As before we declare what we expect: ```kotlin val expected = Answer.Success(LoginStatus.INVALID_CREDENTIALS) ``` Now we need to mock the response on our RemoteSource, since we actually expect some kind of response from it. To do this we add the following line: ```kotlin whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))).doReturn(LoginStatusResponses.InvalidCredentials) ``` This means whenever our `mockLoginRemoteSource` login function is called with an argument equal to `LoginCredentials("a", "b")`, then `LoginStatusResponses.InvalidCredentials` is returned. Otherwise by default usually null is returned. It reads nicely in my opinion. Next our invocation: ```kotlin val actual = sut.invoke(LoginCredentials("a", "b")) ``` And finally verification: ```kotlin Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockUserDataLocalStorage) ``` Together: ```kotlin @DisplayName("GIVEN invalid credentials response WHEN trying to login THEN invalid credentials is returned") @Test fun invalidLoginResponseReturnInvalidCredentials() = runBlockingTest { val expected = Answer.Success(LoginStatus.INVALID_CREDENTIALS) whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))) .doReturn(LoginStatusResponses.InvalidCredentials) val actual = sut.invoke(LoginCredentials("a", "b")) Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockUserDataLocalStorage) } ``` Now we see how we can mock responses. ### 4. `validResponseResultsInSavingSessionAndSuccessReturned`, Now continue with `validResponseResultsInSavingSessionAndSuccessReturned`, You should have almost every tool to do this test: - declare the expected value - do the mock response - call the system under test - verify the actual result to the expected - verify the localStorage's session was saved once, and only once: `verify(mockUserDataLocalStorage, times(1)).session = Session("c", "d")` - verify the localStorage was not touched anymore. The full code: ```kotlin @DisplayName("GIVEN success response WHEN trying to login THEN session is saved and success is returned") @Test fun validResponseResultsInSavingSessionAndSuccessReturned() = runBlockingTest { val expected = Answer.Success(LoginStatus.SUCCESS) whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))) .doReturn(LoginStatusResponses.Success(Session("c", "d"))) val actual = sut.invoke(LoginCredentials("a", "b")) Assertions.assertEquals(expected, actual) verify(mockUserDataLocalStorage, times(1)).session = Session("c", "d") verifyNoMoreInteractions(mockUserDataLocalStorage) } ``` ### 5. `invalidResponseResultsInErrorReturned` this is really similar to our previous test, however now somehow we have to mock throwing an exception to do this let's create an exception: ```kotlin val exception = RuntimeException() ``` declare our expected value: ```kotlin val expected = Answer.Error(UnexpectedException(exception)) ``` Do the mocking: ```kotlin whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))).doThrow(exception) ``` invocation: ```kotlin val actual = sut.invoke(LoginCredentials("a", "b")) ``` verification: ```kotlin Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockUserDataLocalStorage) - Now we saw how to mock invocations on our mock objects - How to test suspend functions - and the pattern of GIVEN-WHEN-THEN description. ``` together: ```kotlin @DisplayName("GIVEN error resposne WHEN trying to login THEN session is not touched and error is returned") @Test fun invalidResponseResultsInErrorReturned() = runBlockingTest { val exception = RuntimeException() val expected = Answer.Error(UnexpectedException(exception)) whenever(mockLoginRemoteSource.login(LoginCredentials("a", "b"))) .doThrow(exception) val actual = sut.invoke(LoginCredentials("a", "b")) Assertions.assertEquals(expected, actual) verifyZeroInteractions(mockUserDataLocalStorage) } ``` ## Our third Class Test with flows Our system under test will be org.fnives.test.showcase.core.content.ContentRepository It has two methods: - getContents: that returns a Flow, which emits loading, error and content data - fetch: which suppose to clear cache and if the flow is observed then start loading The content data come from a RemoteSource class. Additionally the Content is cached. So observing again should not yield loading. The inner workings of the class shouldn't matter, just the public apis, since that's what we want to test. For setup we declare the system under test and it's mock argument. ```kotlin private lateinit var sut: ContentRepository private lateinit var mockContentRemoteSource: ContentRemoteSource @BeforeEach fun setUp() { mockContentRemoteSource = mock() sut = ContentRepository(mockContentRemoteSource) } ``` ### 1. `fetchingIsLazy` As usual we are staring with the easiest test. We verify that the request is not called until the flow is not touched. So just verify the request is not called yet: ```kotlin @DisplayName("GIVEN no interaction THEN remote source is not called") @Test fun fetchingIsLazy() { verifyNoMoreInteractions(mockContentRemoteSource) } ``` ### 2. `happyFlow` Next logical step is to verify the Happy flow. We setup the request to succeed and expect a Loading and Success state to be returned. ```kotlin val expected = listOf( Resource.Loading(), Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) ) whenever(mockContentRemoteSource.get()).doReturn(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) ``` Next the action: ```kotlin val actual = sut.contents.take(2).toList() ``` Now just the verifications ```kotlin Assertions.assertEquals(expected, actual) ```` Note we don't verify the request has been called, since it's implied. It returns the same data we returned from the request, so it must have been called. ### 3. ```errorFlow``` This is really similar to the happy flow, only we throw and expect specific errors: ```kotlin val exception = RuntimeException() val expected = listOf( Resource.Loading(), Resource.Error>(UnexpectedException(exception)) // Note since RuntimeException is not usually sent from NetworkRequest we expect an UnexpectedException. ) whenever(mockContentRemoteSource.get()).doThrow(exception) ``` The action and verification stays the same: ```koltin val actual = sut.contents.take(2).toList() Assertions.assertEquals(expected, actual) ``` ### 4. `verifyCaching` Still sticking to just that function, we should verify it's caching behaviour, aka if a data was loaded once the next time we observe the flow that data is returned: The setup is similar to the happy flow, but take a look at the last line closely ```kotlin val content = Content(ContentId("1"), "", "", ImageUrl("")) val expected = listOf(Resource.Success(listOf(content))) whenever(mockContentRemoteSource.get()).doReturn(listOf(content)) sut.contents.take(2).toList() // note this is part of the setup since we want the class in a state where it has a cache! ``` The action will only take one element which we expect to be the cache ```kotlin val actual = sut.contents.take(1).toList() ``` In the verification state, we will also make sure the request indead was called only once: ```kotlin verify(mockContentRemoteSource, times(1)).get() Assertions.assertEquals(expected, actual) ``` ### 5. `loadingIsShownBeforeTheRequestIsReturned` So far we just expected the first element is "loading", but it could easily happen that the flow set up in such a way that the loading is not emitted before the request already finished. This can be an easy mistake with such flows, but would be really bad UX, so let's see how we can verify something like that: We need to suspend the request calling and verify that before that is finished the Loading is already emitted. So the issue becomes how can we suspend the mock until a signal is given. Generally we could still use mockito mocks OR we could create our own Mock. #### Creating our own mock. We can simply implement the interface of ContentRemoteSource. Have a it's method suspend until a signal. Something along the way of: ```kotlin class SuspendingContentRemoteSource { private var completableDeferred = CompletableDeferred() @Throws(NetworkException::class, ParsingException::class) suspend fun get(): List { completableDeferred = CompletableDeferred() completableDeferred.await() return emptyList() } fun signal() = completableDeferred.complete(Unit) } ``` In this case we should recreate our sut in the test and feed it our own remote source. #### Still using mockito To mock such behaviour with mockito with our current tool set is not as straight forward as creating our own. That's because how we used mockito so far it is not aware of the nature of suspend functions, like our code is in the custom mock. However mockito give us the arguments passed into the function. And since we know the Continuation object is passed as a last argument in suspend functions we can take advantage of that. This then can be abstracted away and used wherever without needing to create Custom Mocks for every such case. To get arguments when creating a response for the mock you need to use thenAnswer { } and this lambda will receive InvocationOnMock containing the arguments. Luckily this has already be done in "org.mockito.kotlin" and it's called `doSuspendableAnswer` The point here is that we can get arguments while mocking with mockito, and we are able to extend it in a way that helps us in common patterns. This `doSuspendableAnswer` wasn't available for a while, but we could still create it, if needed. #### Back to the actual test Our setup as mentioned will suspend the request answer but expect a Loading state regardless: ```kotlin val expected = Resource.Loading>() val suspendedRequest = CompletableDeferred() whenever(mockContentRemoteSource.get()).doSuspendableAnswer { suspendedRequest.await() emptyList() } ``` Our action simply takes the first element: ```kotlin val actual = sut.contents.take(1).toList() ``` In verification we verify that value is as expected and clean up the suspension of the request (just so it's explicit what we are testing) ```kotlin Assertions.assertEquals(listOf(expected), actual) suspendedRequest.complete(Unit) ``` ### 6. `whenFetchingRequestIsCalledAgain` We still didn't even touch the fetch method so let's test the that behaviour next: However the main issue here is, when to call fetch. If we call after `take()` we will never reach it, but if we call it before then it doesn't test the right behaviour. We need to do it async, but async means it's not linear, thus our request could become shaky. For this we will use TestCoroutineDispatcher. Let's add this to our setup: ```kotlin private lateinit var sut: ContentRepository private lateinit var mockContentRemoteSource: ContentRemoteSource private lateinit var testDispatcher: TestCoroutineDispatcher @BeforeEach fun setUp() { testDispatcher = TestCoroutineDispatcher() testDispatcher.pauseDispatcher() // we pause the dispatcher so we have full control over it mockContentRemoteSource = mock() sut = ContentRepository(mockContentRemoteSource) } ``` Next we should use the same dispatcher in our test so: ```kotlin fun whenFetchingRequestIsCalledAgain() = runBlockingTest(testDispatcher) { } ``` Okay with this we should write our setup: ```kotlin val exception = RuntimeException() val expected = listOf( Resource.Loading(), Resource.Success(emptyList()), Resource.Loading(), Resource.Error>(UnexpectedException(exception)) ) var first = true whenever(mockContentRemoteSource.get()).doAnswer { if (first) emptyList().also { first = false } else throw exception // notice first time we return success next we return error } ``` Our action will need to use async and advance to coroutines so we can are testing the correct behaviour: ```kotlin val actual = async(testDispatcher) { sut.contents.take(4).toList() } testDispatcher.advanceUntilIdle() // we ensure the async is progressing as much as it can (thus receiving the first to values) sut.fetch() testDispatcher.advanceUntilIdle() // ensure the async progresses further now, since we give it additional action to take. ``` Our verification as usual is really simple ```kotlin Assertions.assertEquals(expected, actual.await()) ``` Now we can test even complicated interactions between methods and classes with TestCoroutineDispatcher. ### 7. `noAdditionalItemsEmitted` Lastly so far we always assumed that we are getting the exact number of values take(4), take(2). However it's possible our flow may send out additional unexpected data. So we also need to test that this assumption is correct. I think the best place to start from is our most complicated test `whenFetchingRequestIsCalledAgain` since this is the one most likely add additional unexpected values. Luckily `runBlockingTest` is helpful here: if a coroutine didn't finish properly it will throw an IllegalStateException. So all we need to do is to request more than elements it should send out and expect an IllegalStateException from runBlocking. So our method looks just like `whenFetchingRequestIsCalledAgain` except wrapped into an IllegalStateException expectation, and requesting 5 elements instead of 4. ```kotlin Assertions.assertThrows(IllegalStateException::class.java) { runBlockingTest(testDispatcher) { val exception = RuntimeException() val expected = listOf( Resource.Loading(), Resource.Success(emptyList()), Resource.Loading(), Resource.Error>(UnexpectedException(exception)) ) var first = true whenever(mockContentRemoteSource.get()).doAnswer { if (first) emptyList().also { first = false } else throw exception } val actual = async(testDispatcher) { sut.contents.take(5).toList() } testDispatcher.advanceUntilIdle() sut.fetch() testDispatcher.advanceUntilIdle() Assertions.assertEquals(expected, actual.await()) } } ``` ## Conclusion Here we went over most common cases when you need to test simple java / kotlin files with no reference to networking or android: - how to setup and structure your test - how to run your tests - a convention to naming your tests - how to use mockito to mock dependencies of your system under test - how to test suspend functions - how to test flows - how to verify your mock usage - how to verify success and error states