Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Redux + (RxKotlin | RxSwift) = Awesome Native Mobile Apps - Unit Testing - Part 5

DZone's Guide to

Redux + (RxKotlin | RxSwift) = Awesome Native Mobile Apps - Unit Testing - Part 5

This chapter shows how to unit test code blocks in your native mobile apps with these useful libraries.

· Mobile Zone ·
Free Resource

These libraries can be a boon for native app developers. See how they change the developer experience in the second part of this series.

We have learned about the store, state, Reducer and ReactiveX usage in part 4, now let's learn about unit testing the code blocks. Dive in...

Let's start with identifying the code blocks that requires testing. To recollect, refer to the below picture of the code blocks

Should we unit test Actions?

No, these are plain classes.

Should we unit test the middleware?

Yes, but only if you decorate the data and errors that you receive from the data provider and dispatch new actions.

Should we unit test the reducers?

Yes, as they create the new states based on Actions.

Should we unit test View (Activities, Fragments or View Controllers)?

No, it should not have any work other than listening to State and responding to user events. These should be validated as UI tests.

Should we unit test the observables?

Yes, it is important we should test the subsequent actions that are employed on observables.

Should we unit test the subscriptions?

Yes, it is important we should test how subscriptions behave on success and error cases.

Now we have the understanding of the testing the needed code blocks, let's look at the testing code

Unit Testing Reducers

Let's test the authentication reducer that should change the state to show the progress bar when the LoginStartedAction is dispatched.

fun authenticationReducer(action: Action, 
                          state: AuthenticationState?): AuthenticationState {

    val newState =  state ?: AuthenticationState(LoggedInState.notLoggedIn,
                                                 userName = "")
    when (action) {

        is LoginStartedAction -> {
            return newState.copy(isFetching = true)
        }
        is SomeOtherAction -> {
           // Do something with state
        }
    }
    return newState
}

Though this code is trivial, in real life you may have a complex state that you are updating, it is important we should test them.

class TestGitHubReducer{

    @Test // @DisplayName("test when loginStartedAction returns isFetching as true")
    fun test_authenticationReducer_for_loginStartedAction(){
        //Given
        val loginStartedAction = LoginStartedAction("")
        val state = AuthenticationState(LoggedInState.notLoggedIn,
                                        isFetching = false,
                                        userName = "")

        // When
        val newState = authenticationReducer(loginStartedAction,state)

        // Then
        assertThat(newState.isFetching).isTrue()
    }
}

Also note - the testing class does not have any extra dependencies and mocks, it is pure JUnit testing class as the reducer is a pure function

Want to look at the all possible use cases for testing reducers - refer here.

Unit Testing Middleware

Observables - Stream Creators

Testing the middleware, particularly the observables, needs a little bit of mocking.

class TestGHLoginObservable {

    @Test  // @DisplayName("Verify LoginTask when passed success returns LoggedInState_as_loggedIn")
    fun test_LoginTask_returns_LoggedInState_as_loggedIn(){
        // Given
        class TestMockGitHubApiService : GitHubApi {
            override fun createToken(username: String, password: String): LoginDataModel {
                return LoginDataModel(userName = username,
                        token = "181818181818181818181818181818",
                        loginStatus = LoggedInState.loggedIn,
                        createdAt = Date()
                )
            }
        }
        val ghLoginTask = GHLoginTask("test","test")
        ghLoginTask.githubService = TestMockGitHubApiService()

        // When
        val ghLoginObserver = ghLoginTask.getGHLoginObservable().test()
        ghLoginObserver.awaitTerminalEvent()

        // Then
        ghLoginObserver.assertNoErrors()
        val values = ghLoginObserver.values()
        assertThat(values.first().first).isInstanceOf(LoginResultAction::class.java)
        assertThat(values.first().first.createdAt)
        .isEqualTo(SimpleDateFormat("MMM dd, yyy").format(Date()))
    }
    }

In the //Given part of the test case, we are creating a mock of the GitHubApi, called TestMockGitHubApiService that implements the interface GitHubApi .We are overriding only the needed methods and injecting them as below:

val ghLoginTask = GHLoginTask("test","test")
ghLoginTask.githubService = TestMockGitHubApiService()

Check the complete Observerable test cases here.

Subscriptions - Stream Consumers

Testing observers or subscriptions needs a little bit of mocking. Let's start with defining them:

class TestGitHubSubscriptions{
    internal data class TestState(var name:String? = null, 
                                  var password:String? = null): StateType
    internal class TestStateReducer {
        lateinit var mAction: Action
        fun handleAction(action: Action, state: TestState?): TestState {
            val newState = state ?: TestState()
            mAction = action
            return newState
        }
    }

    private val testStateReducer = TestStateReducer()
    private lateinit var testStore: Store<TestState>

    @Before
    fun setUp(){
        testStore = Store(
                reducer = testStateReducer::handleAction,
                state = TestState(),
                middleware = arrayListOf()
        )
    }

    @Test  // @DisplayName("Verify Subscription when passed success dispatches LoggedInDataSaveAction")
    fun test_subscription_when_passed_success_dispatches_LoggedInDataSaveAction(){
        //Given
        val loginResultAction = LoginResultAction(userName = "test",
                loginStatus = LoggedInState.loggedIn,
                token = "161816181618")
        // When
        val ghLoginSubscriber = LoginMiddleWare.getGHLoginSingleSubscriber()
        ghLoginSubscriber.onSuccess(
            Pair(loginResultAction, testStore as Store<StateType>))
        // Then
        Assertions.assertThat(testStateReducer.mAction).
        isInstanceOf(LoggedInDataSaveAction::class.java)
    }
}

If you notice the code carefully, we are testing whether the observers (aka subscriptions) are dispatching the needed actions based the logic. Want to recollect the subscription logic - refer here or here.

To test the subscription, since they dispatching an action to the store, we need to mock the state, store and the reducer.

If you look the reducer code, it merely records the action that has been passed, which is used in the assertion of the JUnit test.

 internal class TestStateReducer {
        lateinit var mAction: Action
        fun handleAction(action: Action, state: TestState?): TestState {
            val newState = state ?: TestState()
            mAction = action
            return newState
        }
    }

I hope you are able to appreciate the fact, you are able to test the code blocks without any Android or iOS intricacies like context. By this time, you may have realized how powerful is these libraries- it helps you unit test the app piece by piece, without hardwiring dependencies. Check the complete Reducer test class here.

In the Next Post...

We will discuss unit testing the router.

Links to the Series

Redux + (RxKotlin | RxSwift) == Awesome Native Mobile Apps - Reactive Programming - Part 4 ⬅ PREVIOUS

Topics:
mobile ,tutorial ,swift ,kotlin ,mobile app development ,unit testing ,mobile testing

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}