Pessoal,
Recentemente falei sobre injeção de dependência (Veja: Parte 1 e Parte 2), de modo a minimizarmos o acoplamento nas nossas aplicações, onde usamos o Ninject como container de Inversão de Controle (IoC – Inversion of Control) e aproveitamos para apresentar o NuGet.
Também falei recentemente sobre o NBuilder, que nos ajuda a criar objetos fake, para usarmos nos nossos testes de unidade.
A idéia agora é mostrar como criar stub’s para utilizarmos nos nossos testes de unidade. Para isso, usamos algum framework de isolamento (Moq, NMock, Rhino Mocks, TypeMock, etc…). Para quem não sabe o que é um stub, ou acha que stub e mock é tudo a mesma coisa, recomendo fortemente a leitura do artigo: Mocks não são Stubs, do Martin Fowler.
Mas porque precisamos de um stub?
Dizemos que um teste não é um teste de unidade se:
- Ele se conecta a um banco de dados;
- Ele se comunica pela rede;
- Ele “toca” o sistema de arquivos;
- Ele não pode rodar junto com algum outro dos seus testes de unidade.
Assim, na prática, usamos mock‘s e stub‘s para “simular” acesso a bancos de dados, comunicação via rede, acesso ao sistemas arquivos, etc… Com isso, dizemos que estamos isolando o sistema que está sendo testado.
Vamos ao código?
Como sempre, vamos direto ao assunto, normalmente é mais fácil de entender a idéia. Vamos utilizar o mesmo exemplo dos post’s citados acima:
Interface:
public Interface ICategoryRepository { IList GetAll(); }
Repositório “verdadeiro”:
public class CategoryRepository : ICategoryRepository { public IList GetAll() { DataContext ctx = new DataContext(); return ctx.Categories; } }
Repositório “fake”:
public class CategoryRepositoryFake : ICategoryRepository { public IList GetAll() { var category1 = new Category() { Id = 1, Name = "Cat 1" }; var category2 = new Category() { Id = 2, Name = "Cat 2" }; var category3 = new Category() { Id = 3, Name = "Cat 3" }; return new List() { category1, category2, category3 }; } }
Controller:
public class CategoryController : Controller { [Inject] public ICategoryRepository Repository { get; set; } public ActionResult Index() { var categories = this.Repository.GetAll(); return View(categories); } }
A nossa motivação agora é eliminar a classe “CategoryRepositoryFake”. Para isso, utilizaremos o Moq que, dada uma interface (no nosso caso “ICategoryRepository”), ele nos “entrega” um objeto fake automaticamente. Na verdade, ele cria um objeto proxy, cuja finalidade é a de “fingir” ser um objeto “real”, para que um outro componente (ou objeto) possa “acreditar” que está falando com um objeto “real”. Basicamente, a mesma coisa que fizemos quando criamos manualmente o objeto fake… Só que o Moq faz isso de forma transparente e automática, e de quebra nos traz uma série de outros benefícios (coisa pra outro post, em breve).
Vamos ver como ficaria um teste de unidade da action “Index”, usando o Moq, NBuilder e o Ninject. Toda a “brincadeira” é feita na seção “Arrange” do teste:
[TestClass()] public class CategoryControllerTest { private IKernel kernel = new StandardKernel(); [TestMethod()] public void Index_Returns_List_Of_Categories() { // Arrange // Cria o mock usando o Moq Mock<ICategoryRepository> categoryRepositoryMock = new Mock<ICategoryRepository>(); // Cria dados "fake" com o NBuilder IEnumerable<Category> categoriesList = Builder<Category>.CreateListOfSize(10).Build(); // Faz o setup do mock usad pelo SUT, para retornar os dados // criados com o NBuilder. Acabamos de criar um STUB. categoryRepositoryMock .Setup(m => m.GetAll()).Returns(categoriesList); // Informamos ao Ninject: Quando alguém precisar de um // ICategoryRepository, entregue o objeto criado pelo mock kernel.Bind<ICategoryRepository>() .ToMethod(m => categoryRepositoryMock.Object); // Act CategoryController sut = kernel.Get<CategoryController>(); ViewResult viewResult = sut.Index(); // Assert IEnumerable<Category> viewData = viewResult.ViewData.Model as IEnumerable<Category>; Assert.IsNotNull(viewData); Assert.AreEqual(10, viewData.Count()); } }
Algumas observações:
Nas linhas 11 e 12, estamos efetivamente criando o nosso objeto mock, baseado na interface “ICategoryRepository”.
Nas linhas 20 e 21, estamos configurando o mock para que, quando alguém invocar o método “GetAll()” do mock, “forçamos” o retorno de uma lista de objetos criada anteriormente, em memória. Acabamos de definir o “tal” stub.
Outro ponto importante está na linha 26. Repare que estamos adicionando ao Ninject uma referência para a interface “ICategoryRepository” apontando para a propriedade “Object” do nosso mock. A propriedade “Object” nos retorna o objeto proxy que comentei anteriormente, e que implementa a interface “ICategoryRepository”.
O que acharam? Nesse post, vimos como criar stub‘s. No próximo, vamos criar mock‘s.
Forte abraço.
Gostei do seu post, muito didático.
Valeu Flávio,
Estou me preparando para “reativar” o blog, em breve vou postar mais sobre esse assunto.
Abraços