I been searching online but I'm unable to find out how testing is made better with MVVM. I get the idea of having a viewModel which interfaces with the view but I don't know how I would write good test cases with MVVM. I have the following ViewModel in Android already:
public class ViewModel extends BaseObservable {
private long countDownTime;
private MyCountDownTimer mCountDownTimer;
private final String TAG = getClass().getSimpleName();
@Bindable
public long getCountDownTime() {
return countDownTime;
}
public void setCountDownTime(long countDownTime) {
this.countDownTime = countDownTime;
notifyPropertyChanged((int) BR.countDownTime);
Log.d(TAG,"prime tick:"+countDownTime);
}
public void startCounting(Long milli){
mCountDownTimer.restartTimer(milli);
}
}
and then I have a xml view that uses it. I also have an activity which actually binds the xml to this view. This activity looks like this:
public class MainActivity extends FragmentActivity {
CountdownBinder mCountdownBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
mCountdownBinder = DataBindingUtil.setContentView(this, R.layout.activity_main);
//Lets reference our textview just for fun
mCountdownBinder.tvGreen.setText("initial text");
ViewModel viewModel = ViewModel.instance();
//now tell databinding about your viewModel below
mCountdownBinder.setViewModel(viewModel);
viewModel.startCounting(200000L);
}
}
Now I'm so confused how to this makes testing better. I read about it but I need a real world example. This code is from the blog here if that matters.
Apparently I can test my unit tests easier right? Would I only test the viewModel in MVVM? What needs to be tested primarily ?
You can still use MVVM without data binding but you need some way to notify the view about data changes, It can be LiveData (preferred way), Java Observable, Rx or even a custom implementation.
Up until now, we've used Data Binding to update the View from the ViewModel. LiveData is a handy data holder that acts as a container over the data to be passed. The best thing about LiveData is that it is lifecycle aware. So if you are in the background, the UI won't try to update.
MVVM stands for Model, View, ViewModel. Model: This holds the data of the application. It cannot directly talk to the View. Generally, it's recommended to expose the data to the ViewModel through Observables. View: It represents the UI of the application devoid of any Application Logic.
Two-way data binding is nothing but updating the data source if there are any changes in the layout and vice versa. Two-way data binding is not applicable for all the views in Android. For example, using two-way data binding in EditText makes sense because users can update the data in the view.
You are correct with your assumption that you only unit test the ViewModel and the Model. The UI itself is not tested via unit tests, though you can also do automated UI tests, which is different from a unit test.
As of now, your example class itself isn't very unit test friendly. One of the main things MVVM is trying to accomplish (other than separation of concerns) is to decouple your code. Your ViewModel should only contain presentation logic, but no business logic. This goes into the Model layer of MVVM.
Your ViewModel is tightly coupled, because you are instantiating MyCountDownTimer
inside your ViewModel. Because of that, you can't do a unit test anymore as every time you test the ViewModel
class, you will also test MyCountDownTimer
. This turns your unit test into an integration test (testing multiple components working together).
A unit tests per definition should only test a very specific type/class or a certain code block. In other words a unit of code, hence the name: unit test. To achieve this, you need to decouple the dependencies of your object you want to test.
You decouple your objects by splitting them into an interface and an implementation, then inject the the concrete implementation into your object, usually via constructor injection.
For example:
public class ViewModel extends BaseObservable {
private long countDownTime;
// Use final keyword here, so mCountDownTimer can only be set in the constructor and never changed
// this enforces the the classes invariants and once initialized, you'll be sure
// that it never can be null, so no need to do null checks before using
private final MyCountDownTimerInterface mCountDownTimer;
private final String TAG = getClass().getSimpleName();
public ViewModel(MyCountDownTimerInterface mCountDownTimer) {
if(countDownTimer == null) {
throw new IllegalArgumentException("countDownTimer can't be null. ");
}
this.mCountDownTimer = countDownTimer;
}
@Bindable
public long getCountDownTime() {
return countDownTime;
}
public void setCountDownTime(long countDownTime) {
this.countDownTime = countDownTime;
notifyPropertyChanged((int) BR.countDownTime);
}
public void startCounting(Long milli) {
this.mCountDownTimer.restartTimer(milli);
}
}
Now, you can test your ViewModel without a concrete instance of your MyCountDownTimer
class.
Since your example only contains behavior and no result testing, you will have to do a behavior test in your, like
startCounting(10L)
, then restartTimer(10L)
must be called in MyCountDownTimerInterface
and getCountDownTime()
must return 10L
.In order to do this, you would have to mock the MyCountDownTimerInterface
interface and pass the mocked object inside. Mocks can be setup to verify that a certain method of the mocked interface is called with a certain parameter.
I can't offer you any code for this, as I am not familiar with the Java/Android Mock frameworks. I'm a C#/.NET developer. But if you don't know how to mock an interface for a behavior driven unit test, ask a new question here on StackOverflow :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With