Introdução ao EasyMock

Introdução ao EasyMock

1. Introdução

No passado, falamos muito sobreJMockiteMockito.

Neste tutorial, daremos uma introdução a outra ferramenta de simulação -EasyMock.

2. Dependências do Maven

Antes de começarmos, vamos adicionar a seguinte dependência ao nossopom.xml:


    org.easymock
    easymock
    3.5.1
    test

A versão mais recente sempre pode ser encontradahere.

3. Conceitos principais

Ao gerar uma simulação,we can simulate the target object, specify its behavior, and finally verify whether it’s used as expected.

Trabalhar com as simulações do EasyMock envolve quatro etapas:

  1. criando uma simulação da classe de destino

  2. registrar seu comportamento esperado, incluindo a ação, resultado, exceções, etc.

  3. usando zombarias em testes

  4. verificando se está se comportando conforme o esperado

Após o término da gravação, passamos para o modo "replay", para que o mock se comporte como gravado ao colaborar com qualquer objeto que o usará.

Eventualmente, verificamos se tudo corre como o esperado.

As quatro etapas mencionadas acima se referem aos métodos emorg.easymock.EasyMock:

  1. mock(…): gera uma simulação da classe de destino, seja uma classe concreta ou uma interface. Uma vez criado, um mock está no modo "gravação", o que significa que o EasyMock gravará qualquer ação que o Mock Object execute e os reproduzirá no modo "replay"

  2. expect(…): com este método, podemos definir expectativas, incluindo chamadas, resultados e exceções, para ações de gravação associadas

  3. replay(…): muda um determinado mock para o modo “replay”. Então, qualquer ação que desencadeie chamadas de método gravadas anteriormente reproduzirá "resultados gravados"

  4. verify(…): verifica se todas as expectativas foram atendidas e se nenhuma chamada inesperada foi realizada em uma simulação **

Na próxima seção, mostraremos como essas etapas funcionam em ação, usando exemplos do mundo real.

4. Um exemplo prático de zombaria

Antes de continuar, vamos dar uma olhada no contexto de exemplo: digamos que temos um leitor do blog de exemplo, que gosta de navegar pelos artigos no site e, em seguida, tenta escrever artigos.

Vamos começar criando o seguinte modelo:

public class exampleReader {

    private ArticleReader articleReader;
    private IArticleWriter articleWriter;

    // constructors

    public exampleArticle readNext(){
        return articleReader.next();
    }

    public List readTopic(String topic){
        return articleReader.ofTopic(topic);
    }

    public String write(String title, String content){
        return articleWriter.write(title, content);
    }
}

Neste modelo, temos dois membros privados: oarticleReader (uma classe concreta) e oarticleWriter (uma interface).

A seguir, vamos zombar deles para verificar o comportamento deexampleReader.

5. Mock com código Java

Vamos começar zombando deArticleReader.

5.1. Zombaria típica

Esperamos que o métodoarticleReader.next() seja chamado quando um leitor pular um artigo:

@Test
public void whenReadNext_thenNextArticleRead(){
    ArticleReader mockArticleReader = mock(ArticleReader.class);
    exampleReader exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();

    verify(mockArticleReader);
}

No código de exemplo acima, nos limitamos estritamente ao procedimento de 4 etapas e simulamos a classeArticleReader.

Although we really don’t care what mockArticleReader.next() returns, we still need to specify a return value for mockArticleReader.next() usandoexpect(…).andReturn(…).

Comexpect(…), EasyMock espera que o método retorne um valor ou lance umException.

Se simplesmente fizermos:

mockArticleReader.next();
replay(mockArticleReader);

EasyMock irá reclamar sobre isso, pois requer uma chamada emexpect(…).andReturn(…) se o método retornar algo.

Se for um métodovoid, podemosexpect sua ação usandoexpectLastCall() assim:

mockArticleReader.someVoidMethod();
expectLastCall();
replay(mockArticleReader);

5.2. Ordem de repetição

Se precisarmos que as ações sejam reproduzidas em uma ordem específica, podemos ser mais rigorosos:

@Test
public void whenReadNextAndSkimTopics_thenAllAllowed(){
    ArticleReader mockArticleReader
      = strictMock(ArticleReader.class);
    exampleReade exampleReader
      = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleReader.ofTopic("easymock")).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

Neste snippet, nósuse strictMock(…) to check the order of method calls. Para simulações criadas pormock(…)estrictMock(…), qualquer chamada de método inesperada causaria umAssertionError.

Para permitir qualquer chamada de método para o mock, podemos usarniceMock(…):

@Test
public void whenReadNextAndOthers_thenAllowed(){
    ArticleReader mockArticleReader = niceMock(ArticleReader.class);
    exampleReade exampleReader = new exampleReader(mockArticleReader);

    expect(mockArticleReader.next()).andReturn(null);
    replay(mockArticleReader);

    exampleReader.readNext();
    exampleReader.readTopic("easymock");

    verify(mockArticleReader);
}

Aqui não esperávamos queexampleReader.readTopic(…) fosse chamado, mas o EasyMock não reclamará. ComniceMock(…), EasyMock agora só se importa se o objeto de destino executou a ação esperada ou não.

5.3. MockingException arremessos

Agora, vamos continuar com a simulação da interfaceIArticleWriter e como lidar com oThrowables esperado:

@Test
public void whenWriteMaliciousContent_thenArgumentIllegal() {
    // mocking and initialization

    expect(mockArticleWriter
      .write("easymock",""))
      .andThrow(new IllegalArgumentException());
    replay(mockArticleWriter);

    // write malicious content and capture exception as expectedException

    verify(mockArticleWriter);
    assertEquals(
      IllegalArgumentException.class,
      expectedException.getClass());
}

No snippet acima, esperamos quearticleWriter seja sólido o suficiente para detectarXSS(Cross-site Scripting) ataques.

Portanto, quando o leitor tenta injetar código malicioso no conteúdo do artigo, o redator deve lançar umIllegalArgumentException. Registramos esse comportamento esperado usandoexpect(…).andThrow(…).

6. Simulação com anotação

O EasyMock também suporta injeção de zombarias usando anotações. To use them, we need to run our unit tests with EasyMockRunner so that it processes @Mock and @TestSubject annotations.

Vamos reescrever os snippets anteriores:

@RunWith(EasyMockRunner.class)
public class exampleReaderAnnotatedTest {

    @Mock
    ArticleReader mockArticleReader;

    @TestSubject
    exampleReader exampleReader = new exampleReader();

    @Test
    public void whenReadNext_thenNextArticleRead() {
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }
}

Equivalente amock(…), um mock será injetado nos campos anotados com@Mock. E esses mocks serão injetados nos campos da classe anotados com@TestSubject.

No snippet acima, não inicializamos explicitamente o campoarticleReader emexampleReader. Ao chamarexampleReader.readNext(), podemos interpor aquele chamado implicitamentemockArticleReader.

Isso porquemockArticleReader foi injetado no campothe articleReader.

Observe que se quisermos usar outro executor de teste em vez deEasyMockRunner, podemos usar a regra de teste JUnitEasyMockRule:

public class exampleReaderAnnotatedWithRuleTest {

    @Rule
    public EasyMockRule mockRule = new EasyMockRule(this);

    //...

    @Test
    public void whenReadNext_thenNextArticleRead(){
        expect(mockArticleReader.next()).andReturn(null);
        replay(mockArticleReader);
        exampleReader.readNext();
        verify(mockArticleReader);
    }

}

7. Mock comEasyMockSupport

Às vezes, precisamos apresentar várias simulações em um único teste e precisamos repetir manualmente:

replay(A);
replay(B);
replay(C);
//...
verify(A);
verify(B);
verify(C);

Isso é feio e precisamos de uma solução elegante.

Felizmente, temos uma classeEasyMockSupport in EasyMock para ajudar a lidar com isso. Éhelps keep track of mocks, such that we can replay and verify them in um lote como este:

//...
public class exampleReaderMockSupportTest extends EasyMockSupport{

    //...

    @Test
    public void whenReadAndWriteSequencially_thenWorks(){
        expect(mockArticleReader.next()).andReturn(null)
          .times(2).andThrow(new NoSuchElementException());
        expect(mockArticleWriter.write("title", "content"))
          .andReturn("BAEL-201801");
        replayAll();

        // execute read and write operations consecutively

        verifyAll();

        assertEquals(
          NoSuchElementException.class,
          expectedException.getClass());
        assertEquals("BAEL-201801", articleId);
    }

}

Aqui nós zombamos dearticleReader earticleWriter. Ao definir esses mocks para o modo “replay”, usamos um método estáticoreplayAll() fornecido porEasyMockSupport, e usamosverifyAll() para verificar seus comportamentos em lote.

Também introduzimos o métodotimes(…) na faseexpect. Ajuda a especificar quantas vezes esperamos que o método seja chamado para evitar a introdução de código duplicado.

Também podemos usarEasyMockSupport por meio da delegação:

EasyMockSupport easyMockSupport = new EasyMockSupport();

@Test
public void whenReadAndWriteSequencially_thenWorks(){
    ArticleReader mockArticleReader = easyMockSupport
      .createMock(ArticleReader.class);
    IArticleWriter mockArticleWriter = easyMockSupport
      .createMock(IArticleWriter.class);
    exampleReader exampleReader = new exampleReader(
      mockArticleReader, mockArticleWriter);

    expect(mockArticleReader.next()).andReturn(null);
    expect(mockArticleWriter.write("title", "content"))
      .andReturn("");
    easyMockSupport.replayAll();

    exampleReader.readNext();
    exampleReader.write("title", "content");

    easyMockSupport.verifyAll();
}

Anteriormente, usamos métodos estáticos ou anotações para criar e gerenciar simulações. Sob o capô, essas simulações estáticas e anotadas são controladas por uma instância globalEasyMockSupport.

Aqui, nós a instanciamos explicitamente e assumimos todas essas zombarias sob nosso próprio controle, por meio de delegação. Isso pode ajudar a evitar confusão se houver algum conflito de nome em nosso código de teste com EasyMock ou se houver casos semelhantes.

8. Conclusão

Neste artigo, apresentamos brevemente o uso básico do EasyMock, sobre como gerar objetos simulados, registrar e reproduzir seus comportamentos e verificar se eles se comportaram corretamente.

Caso você possa estar interessado, verifiquethis article para uma comparação entre EasyMock, Mocket e JMockit.

Como sempre, a implementação completa pode ser encontradaover on Github.