Abu Noman
Abu Noman

Reputation: 455

Getting NullPointerException for subscribing to Observable in ViewModel during Unit Test

I'm trying to run a unit test on my ViewModel class but when I run the test I am getting a NullPointerException. getDataManager().getAuthToken() method call an API using retrofit and return a string response. Is there any way to test the method 'startLogin()'? Here is the code.

LoginViewModelTest.kt

@RunWith(JUnit4::class)
class LoginViewModelTest {
    @Rule
    @JvmField
    var instantTaskExecutorRule =  InstantTaskExecutorRule()

    companion object {
        @ClassRule
        @JvmField
        val schedulers = RxSchedulerRule()
    }
    private val application = mock(Application::class.java)
    private val dataManager = mock(DataManager::class.java)
    private val serviceConnector = mock(ServiceConnector::class.java)
    private val requestInterceptor = mock(RequestInterceptor::class.java)

    private lateinit var compositeDisposable: CompositeDisposable
    private lateinit var loginViewModel: LoginViewModel

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        compositeDisposable = CompositeDisposable()
        loginViewModel = LoginViewModel(application, dataManager, serviceConnector, compositeDisposable, requestInterceptor)
    }

    @Test
    fun testLoginWithValidQR(){

        val map = QueryMapBuilder.getAuthTokenHeaders()
        val body = TokenReqBody()
        val success = TokenResSuccess()

        `when`(dataManager.getAuthToken(map, body)).thenReturn(Observable.just(success))

        assertNotNull(dataManager.getAuthToken(map, body))

        // got error here
        loginViewModel.startLogin()

    }
}

LoginViewModel.java

public class LoginViewModel extends BaseViewModel {
    private RequestInterceptor mRequestInterceptor;
    private SingleLiveEvent<LoginViewDataEvents> loginViewDataSingleLiveEvent;

    @Inject
    public LoginViewModel(Application application, DataManager dataManager, ServiceConnector serviceConnector, CompositeDisposable compositeDisposable, RequestInterceptor requestInterceptor) {
        super(application, dataManager, serviceConnector, compositeDisposable);
        mRequestInterceptor = requestInterceptor;
        loginViewDataSingleLiveEvent = new SingleLiveEvent<>();
    }

    public void startLogin() {
        Map<String, String> map = QueryMapBuilder.getAuthTokenHeaders();
        TokenReqBody body = QueryMapBuilder.getAuthTokenBody(AppConfig.getConfig());
        getCompositeDisposable().add(getDataManager().getAuthToken(map, body)
                .subscribeOn(Schedulers.io()) //getting error here
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe()
            );
    }

}

Error Logs

java.lang.NullPointerException
at com.example.doc.ui.login.LoginViewModel.startLogin(LoginViewModel.java:102)
at com.example.doc.login.LoginViewModelTest.testLoginWithValidQR(LoginViewModelTest.kt:86)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at com.example.doc.RxSchedulerRule$apply$1.evaluate(RxSchedulerRule.kt:44)

Upvotes: 1

Views: 1536

Answers (2)

arungiri_10
arungiri_10

Reputation: 988

The issue is, in unit test you are creating a new object for DataManager

private val dataManager = mock(DataManager::class.java) 

and using that to call dataManager.getAuthToken(map, body);

However, in actual implementation inside LoginViewModel, you use the below line,

getDataManager().getAuthToken(map, body)

Here getDataManager() is null. So you need to add below line make it work,

when(getDataManager()).thenReturn(dataManager);

Upvotes: 1

Nizan Ifrach
Nizan Ifrach

Reputation: 141

The reason for getting null inside startLogin function is because java passes non-primitive variables by reference, meaning that it passes memory address and not the value itself. The first use of dataManager.getAuthToken(map, body) passes because you are using the same objects map and body that defined the 'when' statement (they have the same memory address). The second time (inside the function), you are using entirely new objects that have a new memory address so the 'when' statement does not trigger. A solution for this is to change the 'when' statement to:

`when`(dataManager.getAuthToken(any(), any())).thenReturn(Observable.just(success))

Which means that the statement will be triggered with any object passed to the function.

Upvotes: 4

Related Questions