在单元测试期间获取 NullPointerException 以订阅 ViewModel 中的 Observable
Getting NullPointerException for subscribing to Observable in ViewModel during Unit Test
我正在尝试 运行 在我的 ViewModel class 上进行单元测试,但是当我 运行 测试时,我收到了 NullPointerException。 getDataManager().getAuthToken() 方法调用 API 使用改造和 return 字符串响应。 'startLogin()' 方法有什么方法可以测试吗?这是代码。
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()
);
}
}
错误日志
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.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.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.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at com.example.doc.RxSchedulerRule$apply.evaluate(RxSchedulerRule.kt:44)
在 startLogin
函数中获取 null 的原因是因为 java 通过引用传递非原始变量,这意味着它传递内存地址而不是值本身。
dataManager.getAuthToken(map, body)
的第一次使用通过,因为您使用的是定义 'when'
语句的相同对象 map
和 body
(它们具有相同的内存地址)。
第二次(在函数内部),您正在使用具有新内存地址的全新对象,因此 'when'
语句不会触发。
一个解决方案是将 'when'
语句更改为:
`when`(dataManager.getAuthToken(any(), any())).thenReturn(Observable.just(success))
这意味着该语句将被传递给函数的 任何 对象触发。
问题是,在单元测试中,您正在为 DataManager 创建一个新对象
private val dataManager = mock(DataManager::class.java)
并用它来调用 dataManager.getAuthToken(map, body);
但是,在 LoginViewModel 内部的实际实现中,您使用以下行,
getDataManager().getAuthToken(map, body)
此处 getDataManager() 为空。所以你需要添加下面一行让它工作,
when(getDataManager()).thenReturn(dataManager);
我正在尝试 运行 在我的 ViewModel class 上进行单元测试,但是当我 运行 测试时,我收到了 NullPointerException。 getDataManager().getAuthToken() 方法调用 API 使用改造和 return 字符串响应。 'startLogin()' 方法有什么方法可以测试吗?这是代码。
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()
);
}
}
错误日志
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.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.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.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at com.example.doc.RxSchedulerRule$apply.evaluate(RxSchedulerRule.kt:44)
在 startLogin
函数中获取 null 的原因是因为 java 通过引用传递非原始变量,这意味着它传递内存地址而不是值本身。
dataManager.getAuthToken(map, body)
的第一次使用通过,因为您使用的是定义 'when'
语句的相同对象 map
和 body
(它们具有相同的内存地址)。
第二次(在函数内部),您正在使用具有新内存地址的全新对象,因此 'when'
语句不会触发。
一个解决方案是将 'when'
语句更改为:
`when`(dataManager.getAuthToken(any(), any())).thenReturn(Observable.just(success))
这意味着该语句将被传递给函数的 任何 对象触发。
问题是,在单元测试中,您正在为 DataManager 创建一个新对象
private val dataManager = mock(DataManager::class.java)
并用它来调用 dataManager.getAuthToken(map, body);
但是,在 LoginViewModel 内部的实际实现中,您使用以下行,
getDataManager().getAuthToken(map, body)
此处 getDataManager() 为空。所以你需要添加下面一行让它工作,
when(getDataManager()).thenReturn(dataManager);