Uso Avançado do JMockit

Uso Avançado do JMockit

1. Introdução

Neste artigo, iremos além do básico do JMockit e começaremos a analisar alguns cenários avançados, como:

  • Falsificação (ou a API MockUp)

  • A classe de utilitário Deencapsulation

  • Como zombar de mais de uma interface usando apenas uma zombaria *Como reutilizar expectativas e verificações

Se você deseja descobrir o básico do JMockit, consulte outros artigos desta série. Você pode encontrar links relevantes na parte inferior da página.

===* 2. Métodos particulares/Classes internas zombando *

Zombar e testar métodos privados ou classes internas geralmente não são considerados boas práticas.

O raciocínio por trás disso é que, se forem privados, não devem ser testados diretamente, pois são a parte mais íntima da classe, mas às vezes isso ainda precisa ser feito, especialmente quando se lida com código legado.

Com o JMockit, você tem duas opções para lidar com elas:

  • A API MockUp para alterar a implementação real (para o segundo caso) *A classe de utilitário Deencapsulation, para chamar qualquer método diretamente (no primeiro caso)

Todos os exemplos a seguir serão feitos para a seguinte classe e vamos supor que sejam executados em uma classe de teste com a mesma configuração da primeira (para evitar a repetição de código):

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

   //default constructor omitted

    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}

====* 2.1 Fingindo com MockUp *

A API de mockup do JMockit fornece suporte para a criação de implementações falsas ou mock-ups. Normalmente, um mock-up visa alguns métodos e/ou construtores da classe a serem falsificados, deixando a maioria dos outros métodos e construtores inalterados. Isso permite uma reescrita completa de uma classe, para que qualquer método ou construtor (com qualquer modificador de acesso) possa ser direcionado.

Vamos ver como podemos redefinir _privateMethod () _ usando a API do Mockup:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp<AdvancedCollaborator>() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

Neste exemplo, estamos definindo um novo MockUp para a classe AdvancedCollaborator usando a anotação _ @ Mock_ em um método com assinatura correspondente. Depois disso, as chamadas para esse método serão delegadas ao nosso que é ridicularizado.

Também podemos usar isso para mock-up o construtor de uma classe que precisa de argumentos ou configuração específicos para simplificar os testes:

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp<AdvancedCollaborator>() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

Neste exemplo, podemos ver que, para zombar de construtores, você precisa zombar do método _ $ init_. Você pode passar um argumento extra do tipo _Invocation, _ com o qual você pode acessar informações sobre a invocação do método simulado, incluindo a instância na qual a invocação está sendo executada.

====* 2.2 Usando a classe Deencapsulation *

O JMockit inclui uma classe de utilitário de teste: o Deencapsulation. Como o nome indica, é usado para descapsular um estado de um objeto e, usando-o, você pode simplificar o teste acessando campos e métodos que não poderiam ser acessados ​​de outra forma.

Você pode invocar um método:

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

Você também pode definir campos:

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

E obtenha campos:

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

E crie novas instâncias de classes:

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

Até novas instâncias de classes internas:

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

Como você pode ver, a classe Deencapsulation é extremamente útil ao testar classes herméticas. Um exemplo pode ser o de definir dependências de uma classe que usa anotações _ @ Autowired_ em campos particulares e não possui setters para elas, ou de testar as unidades de classes internas sem precisar depender da interface pública de sua classe de contêiner.

===* 3. Zombando de várias interfaces em uma mesma zombaria *

Vamos supor que você deseja testar uma classe - ainda não implementada - mas você tem certeza de que ela implementará várias interfaces.

Normalmente, você não seria capaz de testar a classe antes de implementá-la, mas com o JMockit você tem a capacidade de preparar testes antecipadamente, zombando de mais de uma interface usando um objeto de simulação.

Isso pode ser alcançado usando genéricos e definindo um tipo que estende várias interfaces. Esse tipo genérico pode ser definido para uma classe de teste inteira ou apenas para um método de teste.

Por exemplo, vamos criar uma simulação para as interfaces List e Comparable de duas maneiras :

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest<MultiMock
  extends List<String> & Comparable<List<String>>> {

    @Mocked
    private MultiMock multiMock;

    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public <M extends List<String> & Comparable<List<String>>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List<String>) any); result = 0;
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

Como você pode ver na linha 2, podemos definir um novo tipo de teste para todo o teste usando genéricos no nome da classe. Dessa forma, MultiMock estará disponível como um tipo e você poderá criar zombarias para isso usando qualquer uma das anotações do JMockit.

Nas linhas de 7 a 18, podemos ver um exemplo usando uma simulação de uma classe múltipla definida para toda a classe de teste.

Se você precisar da simulação de multi-interface para apenas um teste, poderá conseguir isso definindo o tipo genérico na assinatura do método e passando uma nova simulação desse novo genérico como argumento do método de teste. Nas linhas 20 a 32, podemos ver um exemplo de fazê-lo para o mesmo comportamento testado que no teste anterior.

===* 4. Reutilizando Expectativas e Verificações *

No final, ao testar as classes, você pode encontrar casos em que está repetindo as mesmas Expectativas e/ou Verificações repetidamente. Para facilitar isso, você pode reutilizar os dois facilmente.

Vamos explicar isso por um exemplo (estamos usando as classes Model, Collaborator e Performer em nosso link:/jmockit-101 [artigo JMockit 101]):

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;

    @Mocked
    private Model model;

    @Tested
    private Performer performer;

    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0;
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }

    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls;
        }};
    }

    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls;
        }
    }

    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

Neste exemplo, você pode ver nas linhas de 15 a 18 que estamos preparando uma expectativa para cada teste, para que model.getInfo () _ sempre retorne _ “foo” _ e para _collaborator.collaborate () _ sempre espere _ "Foo" _ como argumento e retornando _true. Colocamos a instrução minTimes = 0 para que nenhuma falha apareça quando na verdade não estiver sendo usada em testes.

Além disso, criamos o método verifyTrueCalls (int) _ para simplificar as verificações para o método _collaborator.receive (booleano) _ quando o argumento passado é _true.

Por fim, você também pode criar novos tipos de expectativas e verificações específicas, apenas estendendo qualquer uma das classes Expectations ou Verifications. Em seguida, você define um construtor se precisar configurar o comportamento e criar uma nova instância desse tipo em um teste, como fazemos nas linhas de 33 a 43.

===* 5. Conclusão *

Com esta parte da série JMockit, abordamos vários tópicos avançados que definitivamente ajudarão você a zombar e testar todos os dias.

Podemos fazer mais artigos sobre o JMockit, portanto, fique atento para aprender ainda mais.

E, como sempre, a implementação completa deste tutorial pode ser encontrada em over no GitHub.

====* 5.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]