android-mvvm
android-mvvm copied to clipboard
[Request] Please add a unit test for the sample project
Hi, your MVVM design is very inspiring.
I made a small sample app which utilizes your modules and Kotlin.
Here is the link https://github.com/mishkaowner/MVVMSample
The only issue I came across is that you are missing a unit test example for your sample application.
I already checked all the test files
ex) https://github.com/manas-chaudhari/android-mvvm/blob/master/android-mvvm/src/test/java/com/manaschaudhari/android_mvvm/FieldUtilsTest.java
But I have not found a clear example of a unit test for ViewModel.
val vm = MainViewModel() vm.edit.set("Hello") vm.result.toObservable().test().assertValue("You typed Hello")
this is the best test code I made so far...
Please, lead me to the right direction.
Thank you
I like to keep the unit tests of ViewModels simple without Rx dependencies.
LoginViewModel loginViewModel = new LoginViewModel();
loginViewModel.email.set("invalid@invalid");
assertEquals("Invalid email", loginViewModel.emailError.get());
However, the dependent values (emailError
in this case) only update after they have been subscribed, which data binding does when the app runs. So, for testing, it will be required for us to setup these subscriptions.
The test()
method in your code does exactly the same. Maybe we can write a function test
that takes an ObservableField
and returns a TestObserver
to reduce the repetition.
Then the code will become
test(vm.result).assertValue("You typed Hello");
Other way is to write a @Before
method and subscribe to all the ObservableFields in the viewModel. Subscribing is simply invoking addOnPropertyChangedCallback
function with an empty callback. Also, we can setup mocks for dependencies here so that it is convenient to assert in unit tests.
This example should give you a good idea.
public class LoginViewModelTest {
private LoginViewModel sut;
private Api mockApi;
private BehaviorSubject<Result<LoginResult>> apiDriver;
private Navigator mockNavigator;
private LoginResult successResult;
private Session mockSession;
private BehaviorSubject<Result<Object>> generateOtpApiDriver;
private MessageHelper mockMessageHelper;
@Before
public void setUp() throws Exception {
mockSession = mock(Session.class);
mockMessageHelper = mock(MessageHelper.class);
successResult = new LoginResult(10, "some_token");
mockApi = mock(Api.class);
apiDriver = BehaviorSubject.create();
generateOtpApiDriver = BehaviorSubject.create();
when(mockApi.login(any(LoginRequest.class))).thenReturn(apiDriver.firstOrError());
when(mockApi.generateOtp(any(OtpRequest.class))).thenReturn(generateOtpApiDriver.firstOrError());
mockNavigator = mock(Navigator.class);
sut = new LoginViewModel(mockApi, mockNavigator, mockSession, mockMessageHelper);
subscribe(sut.getEmailError(), sut.getPasswordError());
}
@Test
public void email_valid() throws Exception {
sut.getEmail().set("[email protected]");
sut.getLoginClick().run();
assertEquals("", sut.getEmailError().get());
}
@Test
public void email_invalid() throws Exception {
sut.getEmail().set("abc_co");
assertEquals("", sut.getEmailError().get());
sut.getLoginClick().run();
assertEquals("Invalid email format", sut.getEmailError().get());
}
@Test
public void password_valid() throws Exception {
sut.getPassword().set("abc_co");
sut.getLoginClick().run();
assertEquals("", sut.getPasswordError().get());
}
@Test
public void password_invalid() throws Exception {
sut.getPassword().set("");
assertEquals("", sut.getPasswordError().get());
sut.getLoginClick().run();
assertEquals("Password cannot be empty", sut.getPasswordError().get());
}
@Test
public void loginClick_ShouldNotInvokeLoginApi_invalidInput() throws Exception {
sut.getEmail().set("testcom");
sut.getPassword().set("testpass");
sut.getLoginClick().run();
verifyNoMoreInteractions(mockApi);
}
@Test
public void loginClick_ShouldInvokeLoginApi_validInput() throws Exception {
sut.getEmail().set("[email protected]");
sut.getPassword().set("testpass");
sut.getLoginClick().run();
verify(mockApi).login(argThat(new ArgumentMatcher<LoginRequest>() {
@Override
public boolean matches(LoginRequest argument) {
return argument.email.equals("[email protected]") &&
argument.password.equals("testpass");
}
}));
}
@Test
public void navigateToHome_onLoginSuccess() throws Exception {
sut.getEmail().set("[email protected]");
sut.getPassword().set("123");
sut.getLoginClick().run();
apiDriver.onNext(ResultFactory.success(successResult));
verify(mockNavigator).navigateToHome();
}
@Test
public void storesAccessToken_onLoginSuccess() throws Exception {
sut.getEmail().set("[email protected]");
sut.getPassword().set("123");
sut.getLoginClick().run();
apiDriver.onNext(ResultFactory.success(successResult));
verify(mockSession).storeAccessToken(successResult.userId, successResult.accessToken);
}
@Test
public void displaysError_onLoginFailure() throws Exception {
TestObserver<String> errorObserver = sut.getLoadingVM().errorMessage.test();
sut.getEmail().set("[email protected]");
sut.getPassword().set("123");
sut.getLoginClick().run();
apiDriver.onNext(ResultFactory.<LoginResult>validationError("Not found"));
verify(mockNavigator, never()).navigateToHome();
errorObserver.assertValue("Not found");
}
@Test
public void displaysProgress_duringLogin() throws Exception {
sut.getEmail().set("[email protected]");
sut.getPassword().set("123");
assertFalse(sut.getLoadingVM().progressVisible.get());
sut.getLoginClick().run();
assertTrue(sut.getLoadingVM().progressVisible.get());
apiDriver.onNext(ResultFactory.<LoginResult>httpErrorUnknown());
assertFalse(sut.getLoadingVM().progressVisible.get());
}
@Test
public void generateOneTimePassword_invokesApi() throws Exception {
sut.getEmail().set("[email protected]");
sut.getGenerateOTPClick().run();
verify(mockApi).generateOtp(argThat(new ArgumentMatcher<OtpRequest>() {
@Override
public boolean matches(OtpRequest argument) {
return argument.email.equals("[email protected]");
}
}));
}
@Test
public void displaysMessage_onSuccess() throws Exception {
sut.getEmail().set("[email protected]");
sut.getGenerateOTPClick().run();
verifyNoMoreInteractions(mockMessageHelper);
generateOtpApiDriver.onNext(ResultFactory.success(new Object()));
verify(mockMessageHelper).showMessage("Check email for OTP");
}
@Test
public void displaysProgressAndError_onFailure() throws Exception {
ObservableField<Boolean> progress = sut.getLoadingVM().progressVisible;
TestObserver<String> errorObserver = sut.getLoadingVM().errorMessage.test();
assertFalse(progress.get());
sut.getGenerateOTPClick().run();
assertTrue(progress.get());
generateOtpApiDriver.onNext(ResultFactory.httpErrorUnknown());
assertFalse(progress.get());
errorObserver.assertValue("Something went wrong");
verifyNoMoreInteractions(mockMessageHelper);
}
@Test
public void signup_navigatesToAddUser() throws Exception {
sut.getSignupClick().run();
verify(mockNavigator).navigateToAddUser();
}
}
Lets keep this issue open as it is important to have an example in the sample app.
Thank you for providing a simple solution!