Lesson 1
Why need MVP? easy to test
simple MVP concept
this example will take later
Lesson2 Building the View
How to build the view
How to create a parent interface that abstracts common Android lifecycle events
my:ex code:https://github.com/roy989898/MyMVPEX
M:hold the java object
P:later,it is empty now
V:the pressive view ,it only receive oder to action,use Fragment to implement
Repositiry: the application get the data form here,but where is the data from ?SOL,file ,memory? the is consider by the Repositiry
First: make the V
create a interface that hold all the action that the View can do
than use the fragment to implement it
Example of the View Interface
public interface UserView {
int getUserId();
void displayFirstName(String name);
void displayLastName(String name);
void showUserNotFoundMessage();
void showUserSavedMessage();
String getFirstName();
String getLastName();
void showUserNameIsRequired();
}
|
example of the Fragment view
public class UserFragment extends Fragment implements UserView {
@Inject UserPresenter userPresenter;
protected EditText userFirstName;
protected EditText userLastName;
private static final String USER_ID = "user_id";
public UserFragment() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MvpApplication)getActivity().getApplication()).getComponent().inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false);
userFirstName = (EditText) v.findViewById(R.id.user_first_name);
userLastName = (EditText) v.findViewById(R.id.user_last_name);
v.findViewById(R.id.user_save).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userPresenter.saveUser();
}
});
return v;
}
@Override
public void onResume() {
super.onResume();
userPresenter.setView(this);
}
@Override
public int getUserId() {
return getArguments() == null ? 0 : getArguments().getInt(USER_ID, 0);
}
@Override
public void displayFirstName(String name) {
userFirstName.setText(name);
}
@Override
public void displayLastName(String name) {
userLastName.setText(name);
}
@Override
public void showUserNotFoundMessage() {
Toast.makeText(getActivity(), R.string.user_not_found, Toast.LENGTH_LONG).show();
}
@Override
public void showUserSavedMessage() {
Toast.makeText(getActivity(), R.string.user_saved, Toast.LENGTH_SHORT).show();
}
@Override
public String getFirstName() {
return userFirstName.getText().toString();
}
@Override
public String getLastName() {
return userLastName.getText().toString();
}
@Override
public void showUserNameIsRequired() {
Toast.makeText(getActivity(), R.string.user_name_required_message, Toast.LENGTH_SHORT).show();
}
}
|
EX:make a view that can receive a name and save it,than show what user name does it save
- create a user model
- create a passive view
Lesson 3 the Presenter
first createt the LifecylePresenter,to handle the life cycel with the fragment
public interface LifecylePresenter {
void resume();
void pause();
}
|
second,creat the UserPresenter that extends LifecylePresenter
public interface UserPresenter extends LifecylePresenter {
void loadUserDetails();
void setView(UserView view);
void saveUser();
}
|
third impl the UserPresenter
public class UserPresenterImpl implements UserPresenter {
private UserView view;
private UserRepository userRepository;
private User u;
public UserPresenterImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public void resume() {
loadUserDetails();
}
@Override
public void pause() {
}
@Override
public void loadUserDetails() {
int userId = view.getUserId();
u = userRepository.getUser(userId);
if(u == null) {
view.showUserNotFoundMessage();
} else {
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
}
}
@Override
public void setView(UserView view) {
this.view = view;
}
@Override
public void saveUser() {
if(u != null) {
if(view.getFirstName().trim().equals("") || view.getLastName().trim().equals("")) {
view.showUserNameIsRequired();
} else {
u.setFirstName(view.getFirstName());
u.setLastName(view.getLastName());
userRepository.save(u);
view.showUserSavedMessage();
}
}
}
}
|
the UserPrestener get the information from the view
how to wire the V-P-M?
v use dagger2 to get p,when dagger inject p to v, it will give the repository(M) to the P,at that time Vp hold P,P hold M,but how the P want to order the V or get information to V? so,P need to hold V,setView!!!!!!
below is the picture
V>P P<V P>R
now,implement by my self.
Lesson 4 use Mockito amd Power Mock to test the prestener
how to useMockito amd Power Mock.see:https://www.blogger.com/blogger.g?blogID=1723759563624018420#editor/target=post;postID=1259495158126674147;onPublishedMenu=overviewstats;onClosedMenu=overviewstats;postNum=0;src=postname
Aim: focus on test the prestener layer,show mock the View amd mock the Repository.
how to mock?
repositiry = mock(UserRepository.class);
view = mock(Userview.class);
|
Lesson 5 what test should write and code Coverage
te tes need to tes every situation of the code.to ensure the code is run as us image
Code Coverage
than you will see
greeen that mean your test has cover if,re,that mean the code not cover by the test
the code coverage has 100% is good
now we write a test to tes the loadUserDetails detail method
if we have a method as belw,what test should we write?
@Override
public void loadUserDetails() {
int userId = view.getUserId();
u = userRepository.getUser(userId);
if(u == null) {
view.showUserNotFoundMessage();
} else {
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
}
}
|
1.we need to test :when loadUserDetails call,getUserId execute 1
getUser execute 1
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
execute 1
showUserNotFoundMessage no execute 1
show
@Test
public void shouldShowErrorMessageOnViewWhenUserIsNotPresent() {
when(mockView.getUserId()).thenReturn(1);
// Return null when we ask the repo for a user.
when(mockUserRepository.getUser(anyInt())).thenReturn(null);
presenter.loadUserDetails();
// Verify repository interactions
verify(mockUserRepository, times(1)).getUser(anyInt());
// verify view interactions
verify(mockView, times(1)).getUserId();
verify(mockView, times(1)).showUserNotFoundMessage();
verify(mockView, never()).displayFirstName(anyString());
verify(mockView, never()).displayLastName(anyString());
}
|
2. the situation of when userRepository.getUser return null
@Test
public void shouldShowErrorMessageOnViewWhenUserIsNotPresent() {
when(mockView.getUserId()).thenReturn(1);
// Return null when we ask the repo for a user.
when(mockUserRepository.getUser(anyInt())).thenReturn(null);
presenter.loadUserDetails();
// Verify repository interactions
verify(mockUserRepository, times(1)).getUser(anyInt());
// verify view interactions
verify(mockView, times(1)).getUserId();
verify(mockView, times(1)).showUserNotFoundMessage();
verify(mockView, never()).displayFirstName(anyString());
verify(mockView, never()).displayLastName(anyString());
}
|
Lesson 5 Getting to 100% Code Coverage
package io.caster.simplemvp.presentation;
import io.caster.simplemvp.model.User;
import io.caster.simplemvp.repository.UserRepository;
import io.caster.simplemvp.view.UserView;
public class UserPresenterImpl implements UserPresenter {
private UserView view;
private UserRepository userRepository;
private User u;
public UserPresenterImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public void resume() {
loadUserDetails();
}
@Override
public void pause() {
}
@Override
public void loadUserDetails() {
int userId = view.getUserId();
u = userRepository.getUser(userId);
if(u == null) {
view.showUserNotFoundMessage();
} else {
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
}
}
@Override
public void setView(UserView view) {
this.view = view;
}
@Override
public void saveUser() {
if(u != null) {
if(view.getFirstName().trim().equals("") || view.getLastName().trim().equals("")) {
view.showUserNameIsRequired();
} else {
u.setFirstName(view.getFirstName());
u.setLastName(view.getLastName());
userRepository.save(u);
view.showUserSavedMessage();
}
}
}
}
|
The Test
package io.caster.simplemvp;
import org.junit.Before;
import org.junit.Test;
import io.caster.simplemvp.model.User;
import io.caster.simplemvp.presentation.UserPresenter;
import io.caster.simplemvp.presentation.UserPresenterImpl;
import io.caster.simplemvp.repository.UserRepository;
import io.caster.simplemvp.view.UserView;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
public class PresenterTests {
UserRepository mockUserRepository;
UserView mockView;
UserPresenter presenter;
User user;
@Before
public void setup() {
mockUserRepository = mock(UserRepository.class);
user = new User();
user.setId(1);
user.setFirstName("Mighty");
user.setLastName("Mouse");
when(mockUserRepository.getUser(anyInt())).thenReturn(user);
mockView = mock(UserView.class);
presenter = new UserPresenterImpl(mockUserRepository);
presenter.setView(mockView);
}
@Test
public void noInteractionsWithViewShouldTakePlaceIfUserIsNull() {
presenter.saveUser();
// user object is not initialized, lets verify no interactions take place
verifyZeroInteractions(mockView);
}
@Test
public void shouldBeABleToLoadTheUserFromTheRepositoryWhenValidUserIsPresent() {
when(mockView.getUserId()).thenReturn(1);
presenter.loadUserDetails();
// Verify repository interactions
verify(mockUserRepository, times(1)).getUser(anyInt());
// Verify view interactions
verify(mockView, times(1)).getUserId();
verify(mockView, times(1)).displayFirstName("Mighty");
verify(mockView, times(1)).displayLastName("Mouse");
verify(mockView, never()).showUserNotFoundMessage();
}
@Test
public void shouldShowErrorMessageOnViewWhenUserIsNotPresent() {
when(mockView.getUserId()).thenReturn(1);
// Return null when we ask the repo for a user.
when(mockUserRepository.getUser(anyInt())).thenReturn(null);
presenter.loadUserDetails();
// Verify repository interactions
verify(mockUserRepository, times(1)).getUser(anyInt());
// verify view interactions
verify(mockView, times(1)).getUserId();
verify(mockView, times(1)).showUserNotFoundMessage();
verify(mockView, never()).displayFirstName(anyString());
verify(mockView, never()).displayLastName(anyString());
}
@Test
public void TestloadUserDetailswhenHaveaVaildUser(){
when(mockView.getUserId()).thenReturn(1);
// (mockUserRepository.getUser already mock
presenter.loadUserDetails();
verify(mockView,times(1)).getUserId();
verify(mockUserRepository,times(1)).getUser(anyInt());
verify(mockView,never()).showUserNotFoundMessage();
verify(mockView,times(1)).displayFirstName("Mighty");
verify(mockView,times(1)).displayLastName("Mouse");
}
@Test
public void shouldShouldErrorMessageDuringSaveIfFirstOrLastNameIsMissing() {
when(mockView.getUserId()).thenReturn(1);
// Load the user
presenter.loadUserDetails();
verify(mockView, times(1)).getUserId();
// Set up the view mock
when(mockView.getFirstName()).thenReturn(""); // empty string
presenter.saveUser();
verify(mockView, times(1)).getFirstName();
verify(mockView, never()).getLastName();
verify(mockView, times(1)).showUserNameIsRequired();
// Now tell mockView to return a value for first name and an empty last name
when(mockView.getFirstName()).thenReturn("Foo");
when(mockView.getLastName()).thenReturn("");
presenter.saveUser();
verify(mockView, times(2)).getFirstName(); // Called two times now, once before, and once now
verify(mockView, times(1)).getLastName(); // Only called once
verify(mockView, times(2)).showUserNameIsRequired(); // Called two times now, once before and once now
}
@Test
public void shouldBeAbleToSaveAValidUser() {
when(mockView.getUserId()).thenReturn(1);
// Load the user
presenter.loadUserDetails();
verify(mockView, times(1)).getUserId();
when(mockView.getFirstName()).thenReturn("Foo");
when(mockView.getLastName()).thenReturn("Bar");
presenter.saveUser();
// Called two more times in the saveUser call.
verify(mockView, times(2)).getFirstName();
verify(mockView, times(2)).getLastName();
assertThat(user.getFirstName(), is("Foo"));
assertThat(user.getLastName(), is("Bar"));
// Make sure the repository saved the user
verify(mockUserRepository, times(1)).save(user);
// Make sure that the view showed the user saved message
verify(mockView, times(1)).showUserSavedMessage();
}
@Test
public void shouldNotHaveInteractionsOnPause() {
presenter.pause();
verifyZeroInteractions(mockUserRepository);
verifyZeroInteractions(mockView);
}
@Test
public void shouldLoadUserDetailsWhenResumeCalled() {
presenter.resume();
verify(mockUserRepository, times(1)).getUser(anyInt());
verify(mockView, times(1)).displayFirstName(anyString());
verify(mockView, times(1)).displayLastName(anyString());
}
@Test(expected = NullPointerException.class)
public void shouldThrowNullPointerExceptionWhenViewIsNull() {
// Null out the view
presenter.setView(null);
// Try to load the screen which will force interactions with the view
presenter.loadUserDetails();
}
}
|
Other is the same ,should test all situation
@Test(expected = NullPointerException.class) can us to test the view is null
Lesson 7 Removing Lifecycle Events from the Presenter
Lesson8 The ViewNotFoundException & Comprehensive Testing
Lest seee the tes in null view
@Test(expected = NullPointerException.class)
public void shouldThrowNullPointerExceptionWhenViewIsNull() {
// Null out the view
presenter.setView(null);
// Try to load the screen which will force interactions with the view
presenter.loadUserDetails();
}
|
@Override
public void loadUserDetails() {
int userId = view.getUserId();
u = userRepository.getUser(userId);
if(u == null) {
view.showUserNotFoundMessage();
} else {
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
}
}
|
if before the view.getUserId(); has some code ,it make NullPointerException appear, the test still pass,so this is a problem
we should crate a Expection that on happen when the view is null
@Test(expected = ViewNullException.class)
public void shouldThrowNullPointerExceptionWhenViewIsNull() {
// Null out the view
presenter.setView(null);
// Try to load the screen which will force interactions with the view
presenter.loadUserDetails();
}
|
public class ViewNullException extends RuntimeException {
public ViewNullException() {
super();
}
public ViewNullException(String detailMessage) {
super(detailMessage);
}
public ViewNullException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public ViewNullException(Throwable throwable) {
super(throwable);
}
}
|
and make it throw it
public void loadUserDetails() {
if(view==null){
throw new ViewNullException();
}
int userId = view.getUserId();
u = userRepository.getUser(userId);
if(u == null) {
view.showUserNotFoundMessage();
} else {
view.displayFirstName(u.getFirstName());
view.displayLastName(u.getLastName());
}
}
|
code coverage 100% not mean yuor test is good,we should test any stiusation
and the production shoud handle all the situation
another example:
view.getFirstName() and view.getLastName() may be null
we should create a Test to test what happen whn the view.getFirstName() and view.getLastName() is null,and change the code top handle them,to show view.showUserNameIsRequired();
show we create the test
@Test
public void shouldBeHandleTheNullFirstName() {
when(mockView.getFirstName()).thenReturn(null);
when(mockView.getUserId()).thenReturn(1);
presenter.setView(mockView);
presenter.loadUserDetails();
verify(mockUserRepository,times(1)).getUser(anyInt());
presenter.saveUser();
verify(mockView,times(1)).getFirstName();
verify(mockView,times(1)).showUserNameIsRequired();
}
|
however ,it throw NullPointExpection
that mean mockView.getFirstName not handle,
change to this
@Override
public void saveUser() {
if(u != null) {
if(view.getFirstName()==null||view.getFirstName().trim().equals("") || view.getLastName().trim().equals("")) {
view.showUserNameIsRequired();
} else {
u.setFirstName(view.getFirstName());
u.setLastName(view.getLastName());
userRepository.save(u);
view.showUserSavedMessage();
}
}
}
|