Dharmendra
Dharmendra

Reputation: 34026

test rxjava callback function

I'm trying to test the code written in callback function of one of Rxjava operator. Here is the original code that I want to test

@Override
public Observable<List<User>> getUsers() {
    UserDataStore userDataStore = userDataStoreFactory.createCloudDataStore();
    return userDataStore.getUsers().map(userEntityDataMapper::transform);
}

In above code there is a "map" operator and its callback Function will transform the original object to another by calling userEntityDataMapper.transform() method. Here I want to test that userEntityDataMapper's transform method must be call. Here is the code which I tried to check if userEntityDataMapper.transform() method calls or not.

@Test
public void testGetUsersHappyCase() {
    List<UserEntity> userEntityList = new ArrayList<>();
    userEntityList.add(new UserEntity());
    given(mockUserDataStore.getUsers()).willReturn(Observable.just(userEntityList));
    List<User> userList = new ArrayList<>();
    given(mockUserEntityDataMapper.transform(userEntityList)).willReturn(userList);
    given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore);

    Observable observable = userDataRepository.getUsers();

    verify(mockUserDataStoreFactory).createCloudDataStore();
    verify(mockUserDataStore).getUsers();
    TestObserver<List<UserEntity>> testObserver = new TestObserver<>();
    TestScheduler testScheduler = new TestScheduler();
    observable.subscribeOn(testScheduler).observeOn(testScheduler).subscribeWith(testObserver);
    verify(mockUserEntityDataMapper).transform(any(List.class));
}

I checked many similar questions on stackoverflow and from forum but not able to find exact solution for my question.

Update: Here is the change which I did to fix the problem.

@Test
public void testGetUsersHappyCase() {
    List<UserEntity> userEntityList = new ArrayList<>();
    userEntityList.add(new UserEntity());
    given(mockUserDataStore.getUsers()).willReturn(Observable.just(userEntityList));
    List<User> userList = new ArrayList<>();
    given(mockUserEntityDataMapper.transform(userEntityList)).willReturn(userList);

    userDataRepository.getUsers().test().assertNoErrors();

    verify(mockUserDataStoreFactory).createCloudDataStore();
    verify(mockUserDataStore).getUsers();

    verify(mockUserEntityDataMapper).transform(userEntityList);
}

Thanks @tynn for the hint of test() method. Also same thing I found in BasicRxJavaSample demo found on https://github.com/googlesamples/android-architecture-components.

Upvotes: 1

Views: 715

Answers (2)

David Rawson
David Rawson

Reputation: 21497

Tynn's answer is correct and you should listen to the advice there. The refactored test looks something like this:

@org.junit.Test
public void testUserDataRepository() throws Exception {
    //arrange
    List<UserEntity> userEntityList = new ArrayList<>();
    userEntityList.add(new UserEntity());
    given(mockUserDataStore.getUsers()).willReturn(Observable.just(userEntityList));
    List<User> userList = new ArrayList<>();
    given(mockUserEntityDataMapper.transform(userEntityList)).willReturn(userList);
    given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore);

    //act
    TestObserver<List<User>> testObservable = userDataRepository.getUsers().test();


    //assert
    verify(mockUserDataStoreFactory).createCloudDataStore();
    verify(mockUserDataStore).getUsers();
    verify(mockUserEntityDataMapper).transform(any(List.class));
}

However there is a greater problem with this type of white box test - the test has degenerated into a reimplementation of the system under test.

If UserEntityDataMapper is lightweight you can use a real version of it in your test. Then your test becomes a black box test and arguably has more value. You will know that the call to UserEntityDataMapper#transform has occurred through the assertion on the correct result of List<User>:

@org.junit.Test
public void testUserDataRepository() throws Exception {
    //arrange
    List<UserEntity> userEntityList = new ArrayList<>();
    userEntityList.add(new UserEntity());
    given(mockUserDataStore.getUsers()).willReturn(Observable.just(userEntityList));
    List<User> userList = new ArrayList<>();
    given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore);

    //act
    TestObserver<List<User>> testObservable = userDataRepository.getUsers().test();

    //assert
    testObservable.assertResult(userList);
}

Upvotes: 0

tynn
tynn

Reputation: 39873

You have to subscribe to the stream in order to execute it. This can be done as easily as calling test() on the observable. This will provide you with a TestObserver.

Additional to this, you don't have any reason to use a TestScheduler. In your case it's actually the issue. You're not calling to triggerActions() and thus your stream is not executed at all.

If you don't modify any scheduler in the code you're testing, just ignore these in your tests as well. If you need to change it, you should better create a rule to set the Schedulers to a synchronous version each. You find setters for this with the RxJavaPlugins class.

Upvotes: 2

Related Questions