android_testin_mockito

¿Cómo hacer TDD en Android? Parte 3 – Mocking & Tests de Integración

Continuamos con la serie de tutoriales para implementar TDD en Android. Aquí veremos los tests de integración y cómo trabajar sobre la capa UI.

Recordemos que en el anterior artículo “¿Cómo hacer TDD en Android? Parte 2 – Tests Unitarios” vimos como implementar los tests unitarios que cubrían todas las pruebas de la capa de negocio.

A continuación entraremos a ver como probar el correcto funcionamiento de nuestra aplicación y su integración con la capa de UI.

Lo primero que haremos será crear una pantalla básica de Login, con su campo de usuario, contraseña y su botón para acceder.

Para ello crearemos nuestra Activity llamada LoginActivity, cuyo xml asociado será el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_login"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/dimen_40dp"
    android:paddingRight="@dimen/dimen_40dp"
    tools:context="com.jamontes79.tdd_ejemplo.login.LoginActivity">

    <EditText
        android:id="@+id/txt_user_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:hint="@string/user_name"
        android:visibility="visible"
        android:inputType="text" />

    <EditText
        android:id="@+id/txt_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/txt_user_name"
        android:layout_centerVertical="true"
        android:layout_marginTop="@dimen/dimen_10dp"
        android:hint="@string/password"
        android:inputType="textPassword" />

    <Button
        android:id="@+id/btn_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/txt_password"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/dimen_10dp"
        android:paddingLeft="@dimen/dimen_10dp"
        android:paddingRight="@dimen/dimen_10dp"
        android:text="@string/login" />

</RelativeLayout>

Siguiendo el modelo MVP tendremos que crear un interfaz que conecte nuestra Activity con nuestro Presenter, lo llamaremos LoginView cuyo código será el siguiente:

public interface LoginView {

    void showErrorMessageForUserPassword();

    void showErrorMessageForMaxLoginAttempt();

    void showLoginSuccessMessage();
}

 

Nuestra Activity deberá implementar el interfaz LoginView, quedando el código resultante como se muestra a continuación:

public class LoginActivity extends AppCompatActivity implements LoginView {

    private LoginPresenter loginPresenter;
    private EditText txtUserName;
    private EditText txtPassword;
    private Button btnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initializePresenter();
        initializeViews();
    }


    private void initializePresenter() {
        loginPresenter = new LoginPresenter(this);
    }

    private void initializeViews() {
        txtUserName = (EditText) findViewById(R.id.txt_user_name);
        txtPassword = (EditText) findViewById(R.id.txt_password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                loginPresenter.checkUserPassword(txtUserName.getText().toString().trim(),
                        txtPassword.getText().toString().trim());
            }
        });
    }
    @Override
    public void showErrorMessageForUserPassword() {
        Snackbar.make(txtPassword, R.string.error_user_password, Snackbar.LENGTH_LONG).show();
    }

    @Override
    public void showErrorMessageForMaxLoginAttempt() {
        Snackbar.make(btnLogin, R.string.error_max_login_attempt, Snackbar.LENGTH_LONG).show();
    }

    @Override
    public void showLoginSuccessMessage() {
        Snackbar.make(btnLogin, R.string.login_ok, Snackbar.LENGTH_LONG).show();
    }
}

 

El siguiente pasó sera adaptar el presentador para que se comunique con la Activity, para ello lo modificaremos de la siguiente forma:

public class LoginPresenter {

    private final LoginView loginView;
    private int currentLoginAttempt = 0;
    private static final int MAX_LOGIN_ATTEMPT = 3;
    private static final String USER = "user";
    private static final String PASSWORD = "password";

    public LoginPresenter (LoginView loginView) {
        this.loginView = loginView;
    }
    public int newLoginAttempt() {
        return ++currentLoginAttempt;
    }

    public boolean isLoginAttemptExceeded() {
        return currentLoginAttempt >= MAX_LOGIN_ATTEMPT;
    }

    public boolean checkUserPassword(String user, String password) {
        boolean ret = true;
        if (isLoginAttemptExceeded()) {
           loginView.showErrorMessageForMaxLoginAttempt();
            ret = false;
        } else if (user.equals(USER) && password.equals(PASSWORD)) {
            loginView.showLoginSuccessMessage();
        } else {
            loginView.showErrorMessageForUserPassword();
            ret = false;
            newLoginAttempt();
        }
        return ret;
    }

}

 

Después de estos cambios nuestros tests ya no compilan, ya que al Presenter debemos pasarle un objeto LoginView como parámetro, pero… ¿Cómo hacemos esto? Aquí es donde entra en juego el Mocking.

¿Qué es el Mocking?

Cuando tenemos un objeto del que queremos probar sus métodos y este depende de otro, debemos simular este objeto en lugar de crear una instancia real del mismo, este proceso de simulación se llama “moking” y lo usamos para realizar pruebas unitarias. 

Para realizar estas simulaciones o moking vamos a usar Mockito.

Para usar Mockito en nuestro proyecto debemos añadir la siguiente referencia en el fichero build.gradle.

testCompile "org.mockito:mockito-core:2.+"
androidTestCompile "org.mockito:mockito-android:2.+"

Ahora podemos mockear nuestro LoginView de la siguiente manera:

loginView= mock(LoginView.class);

Ahora ya podemos pasar este objeto a nuestro LoginPresenter, quedando el test de la siguiente manera:

@Test
public void checkIfLoginAttemptIsExceeded(){

        LoginPresenter loginPresenter = new LoginPresenter(loginView);
        Assert.assertEquals(1,loginPresenter.newLoginAttempt());
        Assert.assertEquals(2,loginPresenter.newLoginAttempt());
        Assert.assertEquals(3,loginPresenter.newLoginAttempt());
        Assert.assertTrue(loginPresenter.isLoginAttemptExceeded());
}

Con esto ya podemos ejecutar el test y vemos como se marca en verde.

Ahora procederemos a realizar los test de integración.

Tests de Integración (Relación entre Vista y Presentador)

Para comprobar que nuestra vista muestra un resultado concreto tras llamar al presentador usaremos el método verify().

@Test
public void checkUserAndPasswordIsCorrect(){
        LoginPresenter loginPresenter = new LoginPresenter(loginView);
        loginPresenter.checkUserPassword("user","password");
        verify(loginView).showLoginSuccessMessage();
}

En el código anterior verificamos que nuestra vista llama al método showLoginSuccessMessage(), por lo que podemos asegurar que nuestro presentador está perfectamente integrado con la vista.

El resto de tests quedan de la siguiente manera:

    @Test
    public void checkUserAndPasswordIsNotCorrect(){
        LoginPresenter loginPresenter = new LoginPresenter(loginView);
        loginPresenter.checkUserPassword("user1","password1");
        verify(loginView).showErrorMessageForUserPassword();
    }

    @Test
    public void checkIfLoginAttemptIsExceededWithMessage(){
        LoginPresenter loginPresenter = new LoginPresenter(loginView);
        loginPresenter.checkUserPassword("user1","password1");
        loginPresenter.checkUserPassword("user1","password1");
        loginPresenter.checkUserPassword("user1","password1");
        loginPresenter.checkUserPassword("user1","password1");
        verify(loginView).showErrorMessageForMaxLoginAttempt();
    }

Puedes descargar el estado actual del proyecto desde el siguiente enlace de github: https://github.com/jamontes79/TDD_Ejemplo/tree/518f5e7e79cdb741f7470360b1b7433eb1810e59.

 

¿Que te parece el uso de Mockito para Android? ¿Lo implementas habitualmente en tus proyectos?

Publicado en Android, Desarrollo y etiquetado , , , , , , , , .

Ingeniero Técnico en Informática de Sistemas. Apasionado de la tecnología y enfocado al desarrollo móvil

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.