2016年8月30日 星期二

MVP and Mock in android from CasterIO

Lesson 1
Why need MVP? easy to test


simple MVP concept


this example will take later
How to build  the view
How to create a parent interface that abstracts common Android lifecycle events




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




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();
           }

       }
   }