Em uma bela sexta feira, você parou e pensou

Mas pra que caralho eu uso mock?

Tá, pode ser que você tenha pensado isso ou não. Antes de respondermos isso precisamos entender como podemos escrever um teste para um componente A sem depender de suas dependências.

Antes de entendermos o problema anterior, por que gostaríamos de fazer isso? Para que não tenhamos de lidar com a complexidade que as dependências irão exercer no SUT!

Para isso nós teríamos de criar um objeto que seja capaz de imitar o comportamento de B. Dessa forma, teríamos o controle completo do componente B dado o contexto do testes sem ter a necessidade de lidarmos a dependência concreta.

Benefícios

A capacidade de usar um objeto que se comporta como outro objeto nos traz:

  • Maior controle da dependência dado o cenário de teste;
  • Melhoria no tempo de feedback;
  • Reflexão de como as classes interagem entre si.

Imitando objetos

Bom, até agora entendemos o benefício de removermos a dependência concreta e adicionarmos um objeto que a imita. Esses mímicos possuem nomes específicos dado o comportamento que realizam.

Fake

São objetos que possuem uma implementação concreta porém mais simples do objeto sendo imitado.

Dummies

São objetos que são utilizados apenas para preencher um parâmetro.

Stubs

São objetos que fornecem retornos previamente definidos às chamadas realizadas durante o teste, permitindo que tenhamos o controle do comportamento da dependência durante o teste.

Spies

São objetos similares ao Stub, mas, além de fornecer respostas previamente definidas, ele também registra informações sobre as interações feitas com eles.

Mocks

São objetos simulados que verificam se certas interações foram realizadas. Sua configuração é feita a partir de expectativas sobre como os métodos devem ser chamados e com quais parâmetros.

Exemplos

Agora que conhecemos o que são Test doubles iremos vê-los na prática, para isso tomemos um exemplo do nosso cotidiano um DataSouce

abstract class RemoteDataSource {
	Future<Response> fetch(String url);
}

Dummies

class DummyDataSource implements DataSource {
    @override
    Future<Response> fetchData(String url) async {
        return Response(
            requestOptions: RequestOptions(path: url),
        );
    }
}

Fake

class FakeDataSource implements DataSource {
  final Map<String, dynamic> fakeResponses;
 
  FakeDataSource(this.fakeResponses);
 
  @override
  Future<Response> fetch(String url) async {
    await Future.delayed(Duration(milliseconds: 100));
 
    if (fakeResponses.containsKey(url)) {
      return Response(
        data: fakeResponses[url],
        requestOptions: RequestOptions(path: url),
      );
    } else {
      return Response(
        statusCode: 404,
        statusMessage: 'Not Found',
        requestOptions: RequestOptions(path: url),
      );
    }
  }
}

Stub

class StubDataSource implements DataSource {
  @override
  Future<Response> fetch(String url) async {
    await Future.delayed(Duration(milliseconds: 50));
 
    if (url.contains('/item1')) {
      return Response(
        data: {'name': 'Item 1', 'price': 10.0},
        requestOptions: RequestOptions(path: url),
      );
    } else if (url.contains('/item2')) {
      return Response(
        data: {'name': 'Item 2', 'price': 20.0},
        requestOptions: RequestOptions(path: url),
      );
    } else {
      return Response(
        statusCode: 500,
        statusMessage: 'Internal Server Error',
        requestOptions: RequestOptions(path: url),
      );
    }
  }
}

Spy

class SpyDataSource implements DataSource {
  int fetchCalls = 0;
 
  @override
  Future<Response> fetch() async {
    fetchCalls++;
    return Response(
      data: {'key': 'spyValue'},
      requestOptions: RequestOptions(path: '/spy'),
    );
  }
}

Mock

class MockDataSource extends Mock implements DataSource {}
 
test('fetch should be called and return mocked value', () async {
    final mockDataSource = MockDataSource();
 
    when(() => mockDataSource.fetch()).thenAnswer(
      (_) async => Response(
        data: {'key': 'mockedValue'},
        requestOptions: RequestOptions(path: '/mock'),
      ),
    );
 
    final response = await mockDataSource.fetch();
 
    expect(response.data, {'key': 'mockedValue'});
 
    verify(() => mockDataSource.fetch()).called(1);
  });
}

Bibliografia

  • Fowler. Martin, “Testing Double.” martinfowler.com. Acessado em 1 de agosto de 2024.
  • Aniche. Mauricio, “Effective Software Testing: A Developer’s Guide”, Manning Publications.