Um guia para as expectativas do JMockit

Um guia para as expectativas do JMockit

*1. Introdução *

Este artigo é a segunda parcela da série JMockit. Você pode ler o link:/jmockit-101 [primeiro artigo], pois estamos assumindo que você já está familiarizado com os conceitos básicos do JMockit.

Hoje vamos nos aprofundar e focar nas expectativas. Mostraremos como definir a correspondência de argumentos mais específica ou genérica e formas mais avançadas de definir valores.

===* 2. Correspondência de valores de argumento *

As abordagens a seguir aplicam-se a Expectations e também a Verifications.

====* 2.1 Campos "Qualquer" *

O JMockit oferece um conjunto de campos utilitários para tornar a correspondência de argumentos mais genérica. Um desses utilitários são os campos anyX.

Eles verificarão se qualquer valor foi passado e existe um para cada tipo primitivo (e a classe de wrapper correspondente), um para seqüências de caracteres e um "universal" do tipo Object.

Vamos ver um exemplo:

public interface ExpectationsCollaborator {
    String methodForAny1(String s, int i, Boolean b);
    void methodForAny2(Long l, List<String> lst);
}

@Test
public void test(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForAny1(anyString, anyInt, anyBoolean);
        result = "any";
    }};

    Assert.assertEquals("any", mock.methodForAny1("barfooxyz", 0, Boolean.FALSE));
    mock.methodForAny2(2L, new ArrayList<>());

    new FullVerifications() {{
        mock.methodForAny2(anyLong, (List<String>) any);
    }};
}

Você deve levar em consideração que, ao usar o campo any, é necessário convertê-lo no tipo esperado. A lista completa de campos está presente em documentation.

====* 2.2 Métodos "com" *

O JMockit também fornece vários métodos para ajudar na correspondência genérica de argumentos. Esses são os métodos withX.

Isso permite uma correspondência um pouco mais avançada que os campos anyX. Podemos ver um exemplo aqui, no qual definiremos uma expectativa para um método que será acionado com uma string contendo foo, um número inteiro igual a 1, um Boolean não nulo e qualquer instância da classe List:

public interface ExpectationsCollaborator {
    String methodForWith1(String s, int i);
    void methodForWith2(Boolean b, List<String> l);
}

@Test
public void testForWith(@Mocked ExpectationsCollaborator mock) throws Exception {
    new Expectations() {{
        mock.methodForWith1(withSubstring("foo"), withNotEqual(1));
        result = "with";
    }};

    assertEquals("with", mock.methodForWith1("barfooxyz", 2));
    mock.methodForWith2(Boolean.TRUE, new ArrayList<>());

    new Verifications() {{
        mock.methodForWith2(withNotNull(), withInstanceOf(List.class));
    }};
}

Você pode ver a lista completa dos métodos withX em documentation do JMockit.

Leve em consideração que os _ _com (Delegado) _ e _comArgThat (Matcher) _ especiais serão cobertos em sua própria subseção.

====* 2.3 Nulo não é nulo *

Algo que é bom entender mais cedo ou mais tarde é que null não é usado para definir um argumento para o qual null foi passado para um mock.

Na verdade, null é usado como* açúcar sintático *para definir que qualquer objeto será passado (portanto, só pode ser usado para parâmetros do tipo de referência). Para verificar especificamente se um determinado parâmetro recebe a referência null, o combinador _withNull () _ pode ser usado.

Para o próximo exemplo, definiremos o comportamento de uma simulação, que deve ser acionada quando os argumentos transmitidos forem: qualquer string, qualquer List e uma referência null:

public interface ExpectationsCollaborator {
    String methodForNulls1(String s, List<String> l);
    void methodForNulls2(String s, List<String> l);
}

@Test
public void testWithNulls(@Mocked ExpectationsCollaborator mock){
    new Expectations() {{
        mock.methodForNulls1(anyString, null);
        result = "null";
    }};

    assertEquals("null", mock.methodForNulls1("blablabla", new ArrayList<String>()));
    mock.methodForNulls2("blablabla", null);

    new Verifications() {{
        mock.methodForNulls2(anyString, (List<String>) withNull());
    }};
}

Observe a diferença: null significa qualquer lista e withNull () _ significa uma referência _null a uma lista. Em particular, isso evita a necessidade de converter o valor no tipo de parâmetro declarado (veja que o terceiro argumento teve que ser lançado, mas não o segundo).

A única condição para poder usar isso é que pelo menos um correspondente de argumento explícito tenha sido usado para a expectativa (um método com método ou um any campo).

====* 2.4. Campo "Horários" *

Às vezes, queremos* restringir * o número de invocações esperadas para um método simulado. Para isso, o JMockit possui as palavras reservadas times, minTimes e maxTimes (todos os três permitem apenas números inteiros não negativos).

public interface ExpectationsCollaborator {
    void methodForTimes1();
    void methodForTimes2();
    void methodForTimes3();
}

@Test
public void testWithTimes(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForTimes1(); times = 2;
        mock.methodForTimes2();
    }};

    mock.methodForTimes1();
    mock.methodForTimes1();
    mock.methodForTimes2();
    mock.methodForTimes3();
    mock.methodForTimes3();
    mock.methodForTimes3();

    new Verifications() {{
        mock.methodForTimes3(); minTimes = 1; maxTimes = 3;
    }};
}

Neste exemplo, definimos que exatamente duas invocações (não uma, não três, exatamente duas) de _methodForTimes1 () _ devem ser feitas usando a linha _times = 2; _.

Em seguida, usamos o comportamento padrão (se nenhuma restrição de repetição for fornecida minTimes = 1; _ é usada) para definir que pelo menos uma chamada será feita para _methodForTimes2 () .

Por fim, usando _minTimes = 1; _ seguido por _maxTimes = 3; _ definimos que entre uma e três invocações ocorreriam para _methodForTimes3 () _.

Leve em consideração que minTimes e maxTimes podem ser especificados para a mesma expectativa, desde que minTimes seja atribuído primeiro. Por outro lado, times só pode ser usado sozinho.

2.5 Correspondência de argumentos personalizados

Às vezes, a correspondência de argumentos não é tão direta quanto simplesmente especificar um valor ou usar alguns dos utilitários predefinidos (anyX ou withX).

Para esses casos, o JMockit conta com a interface http://hamcrest.org/[HamcrestGirls Matcher. Você só precisa definir um correspondente para o cenário de teste específico e usá-lo com uma chamada _withArgThat () _.

Vamos ver um exemplo para corresponder uma classe específica a um objeto passado:

public interface ExpectationsCollaborator {
    void methodForArgThat(Object o);
}

public class Model {
    public String getInfo(){
        return "info";
    }
}

@Test
public void testCustomArgumentMatching(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{
        mock.methodForArgThat(withArgThat(new BaseMatcher<Object>() {
            @Override
            public boolean matches(Object item) {
                return item instanceof Model && "info".equals(((Model) item).getInfo());
            }

            @Override
            public void describeTo(Description description) { }
        }));
    }};
    mock.methodForArgThat(new Model());
}

*3. Retornando valores *

Vamos agora olhar para os valores de retorno; lembre-se de que as abordagens a seguir se aplicam apenas a Expectations, pois nenhum valor de retorno pode ser definido para Verifications.

====* 3.1 Resultado e devoluções (…) *

Ao usar o JMockit, você tem três maneiras diferentes de definir o resultado esperado da chamada de um método simulado. Dos três, falaremos agora sobre os dois primeiros (os mais simples) que certamente abrangerão 90% dos casos de uso diários.

Estes dois são o campo result e o método _returns (Object…) _:

 *Com o campo _result_, você pode definir* um * valor de retorno para qualquer método simulado de retorno não nulo. Esse valor de retorno também pode ser uma exceção a ser lançada (desta vez trabalhando para os métodos de retorno nulo e nulo).
 *Várias atribuições de campo _result_ podem ser feitas para retornar* mais de um valor * para mais de uma invocação de método (você pode misturar valores de retorno e erros a serem lançados).
* O mesmo comportamento será alcançado ao atribuir a _result_ uma lista ou uma matriz de valores (do mesmo tipo que o tipo de retorno do método simulado, sem exceções aqui).
 *O método _returns (Object…) _ é* açúcar sintático *para retornar vários valores ao mesmo tempo.

Isso é mostrado mais facilmente com um trecho de código:

public interface ExpectationsCollaborator{
    String methodReturnsString();
    int methodReturnsInt();
}

@Test
public void testResultAndReturns(@Mocked Foo mock){
    new StrictExpectations() {{
        mock.methodReturnsString();
        result = "foo";
        result = new Exception();
        result = "bar";
        mock.methodReturnsInt(); result = new int[] { 1, 2, 3 };
        mock.methodReturnsString(); returns("foo", "bar");
        mock.methodReturnsInt(); result = 1;
    }};

    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    try {
        mock.methodReturnsString();
    } catch (Exception e) { }

    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
    assertEquals("Should return 2", 2, mock.methodReturnsInt());
    assertEquals("Should return 3", 3, mock.methodReturnsInt());
    assertEquals("Should return foo", "foo", mock.methodReturnsString());
    assertEquals("Should return bar", "bar", mock.methodReturnsString());
    assertEquals("Should return 1", 1, mock.methodReturnsInt());
}

Neste exemplo, definimos que, para as três primeiras chamadas a methodReturnsString () _, os retornos esperados são (em ordem) _ "foo" _, uma exceção e _ "bar" _. Conseguimos isso usando três atribuições diferentes para o campo _result.

Em seguida, na* linha 14 *, definimos que para a quarta e a quinta chamadas, _ "foo" _ e _ "bar" _ devem ser retornadas usando o método _returns (Object …​) _.

Para o methodReturnsInt () _, definimos na linha 13 para retornar 1, 2 e, finalmente, 3 atribuindo uma matriz com os diferentes resultados ao campo _result e na linha 15 definimos para retornar 1 por uma atribuição simples ao campo result.

Como você pode ver, existem várias maneiras de definir valores de retorno para métodos simulados.

3.2 Delegadores

Para finalizar o artigo, abordaremos a terceira maneira de definir o valor de retorno: a interface Delegate. Essa interface é usada para definir valores de retorno mais complexos ao definir métodos simulados.

Vamos ver um exemplo simplesmente para explicar:

public interface ExpectationsCollaborator {
    Object methodForDelegate(int i);
}

@Test
public void testDelegate(@Mocked ExpectationsCollaborator mock) {
    new Expectations() {{

        mock.methodForDelegate(anyInt);
        result = new Delegate() {
            public int delegate(int i) throws Exception {
                if(i < 3) {
                    return 5;
                } else {
                    throw new Exception();
                }
            }
        };
    }};

    assertEquals("Should return 5", 5, mock.methodForDelegate(1));
    try {
        mock.methodForDelegate(3);
    } catch (Exception e) { }
}

A maneira de usar um delegador é criar uma nova instância para ele e atribuí-la a um campo returns. Nesta nova instância, você deve criar um novo método com os mesmos parâmetros e tipo de retorno que o método simulado (você pode usar qualquer nome para ele). Dentro deste novo método, use a implementação que você deseja para retornar o valor desejado.

No exemplo, fizemos uma implementação na qual 5 deve ser retornado quando o valor passado para o método simulado é menor que 3 e uma exceção é lançada caso contrário (observe que tivemos que usar _times = 2; _ para que a segunda chamada seja esperado, pois perdemos o comportamento padrão definindo um valor de retorno).

Pode parecer bastante código, mas, nos mesmos casos, será a única maneira de alcançar o resultado que queremos.

*4. Conclusão *

Com isso, praticamente mostramos tudo o que precisamos para criar expectativas e verificações para nossos testes diários.

Naturalmente, publicaremos mais artigos no JMockit, portanto, fique atento para aprender ainda mais.

E, como sempre, a implementação completa deste tutorial pode ser encontrada em https://github.com/eugenp/tutorials/tree/master/testing-modules/mocks/src/test/java/org//mocks/jmockit [ o projeto GitHub].

====* 4.1 Artigos da série *

Todos os artigos da série:

  • link:/jmockit-101 [JMockit 101]

  • link:/jmockit-expectations [Um guia para as expectativas do JMockit]

  • link:/jmockit-advanced-use [Uso avançado do JMockit]