O Rhino Mocks é um framework para geração de mocks, artifício muito utilizado na abordagem de desenvolvimento TDD. Um grande problema é que o Rhino Mocks, ao contrário de frameworks como o Moq, não conseguem “mockear” métodos e propriedades com modificadores de acesso restrito, como protected e internal. Isso força o desenvolvedor a definir como public todos os tipos e membros implementados, o que acarreta em uma exposição indesejada e mas forçada do que não é verdadeiramente público.
O que fazer?
Existem alguns caminhos a se seguir: mudar de framework, implementar duas interfaces para cada classe (uma com a exposição do que realmente se deve expor para quem irá utilizar a classe e outra com todos os métodos e propriedades expostos, para serem utilizados pelos casos de teste), entre outras formas possíveis. Vou apresentar duas dessas formas neste artigo: mock de modificadores internal e protected.
Algumas definições
- internal
O tipo ou membro internal pode ser acessado por qualquer código implementado no mesmo assembly.
- protected
O tipo ou membro protected pode ser acessado apenas dentro da classe que ele for implementado ou em classes que especialize a classe que tem o mesmo implementado.
Modificador de acesso: internal
Para exemplificar, de uma forma mais didática, o mock destes dois tipos de modificadores, eu criei um cenário bem simples. Vejamos na pratica:
1. Criei um projeto “Dominio”:
2. Implementei duas classes :
- Pedido:
public class Pedido { public virtual List<Item> itens{get;set;} public virtual decimal ValorTotal { get; set; } public Pedido() { this.itens = new List<Item>(); } public virtual void AdicionarItem(Item item) { CalcularTotalDoPedido(item); this.itens.Add(item); } Internal virtual void CalcularTotalDoPedido(Item item) { throw new NotImplementedException() } }
- Itens:
public class Item { public virtual Pedido Pedido { get; set; } public Item() { } }
O código visa incluir um item no pedido e também calcular o seu total. Perceba que foi implementado o método AdicionarItem e que ele é público, já o método CalcularTotalDoPedido é internal e não está implementado.
3. Criei um projeto de testes “Dominio.Testes”:
4. Criei uma classe de testes, “Pedido.Testes”:
Note que não tenho acesso ao método internal CalcularTotalDoPedido. Normalmente, os casos de teste estão implementados em um outro assembly. Sendo assim, não consigo gerar o mock de alguém que seja internals, porém existe uma brecha para se fazer isso sem muito esforço.
É preciso informar ao assembly que todos os internals serão visíveis para um assembly específico. Existe um arquivo dentro de properties do projeto, chamado AssemblyInfo.cs, conforme a figura:
5. Inclua no final do arquivo o seguinte trecho de código:
//Para visibilidade dentro do assembly de testes //Perceba que o assembly é o assembly de testes [assembly: InternalsVisibleTo("Dominio.Testes")]
Se acessarmos o caso de teste implementado, veremos que agora nós temos acesso ao método internal CalcularTotalDoPedido, e sendo assim, podemos fazer o mock do mesmo:
Agora conseguimos mockear e até testar, se for o caso, um método de modificador internal.
Modificador de acesso: protected
Antes de mais nada, preciso explicar o que percebi e que me possibilita mockear ou até mesmo testar algo protegido.
Sabemos por definição que o tipo ou membro protected pode ser acessado somente dentro da classe que ele for implementado ou em classes que especializão a classe que tem o mesmo implementado.
Logo, se “Classe B” especializa a “Classe A”, então, a “Classe B” tem acesso na sua implementação a tipos ou membros da “Classe A”. O natural é que ambas as instâncias de B e A não exponham o método protegido, como vemos a seguir:
No entanto, se a “Classe B” instancia ela mesma, então, temos acesso a todo conteúdo protected da “Clase A” na instância da “Classe B”, ficando assim:
Caso a classe C especialize a classe A:
Obteremos o mesmo resultado quando tentamos instanciar a “Classe B” e acessar métodos protected:
Mas se tentarmos instanciar a “Classe C”, nela mesma teremos:
Com isso, conclui-se que, dado uma classe X que tenha tipos e membros protected e instancie a si mesma em um objeto x, o objeto x irá expor todos os tipos ou membros. Isso nos possibilita gerar um mock e testar.
Vamos aproveitar o mesmo cenário, porém trabalhando com o modificador protected.
O método da classe Pedido CalcularTotalDoPedido agora será protected, o que piorou a história, pois preciso fazer um mock dele e não tenho acesso.
Vamos recordar como funciona o modificador protected:
Visto que uma classe especialista (herança) da classe Pedido tem acesso aos tipos ou métodos protected, temos um caminho para fazermos o teste de Pedidos:
A classe PedidoTestes irá especializar Pedido. Ok, mas e daí? Perceba que isso possibilita testar a classe Pedidos indiretamente. O código vai ficar assim:
Veja que ao invés de mockear o pedido, tendo ele como alvo direto, eu faço o mock de PedidoTestes:
var pedido = repositorioMock.PartialMock<PedidoTestes>();
E em seguida eu gero uma expectativa para CalcularTotalDoPedido que é protegida na implementação real.
pedido.Expect(p => p.CalcularTotalDoPedido(null)).IgnoreArguments();
Rodando o teste:
Acessando um serviço que consumiria a classe Pedido, vemos que o método Calcular CalcularTotalDoPedido não é visível.
Vimos a possibilidade de gerar mock de tipos ou membros internal e protected apenas com os recursos de orientação a objetos e do .Net. Sendo assim, você pode continuar usando o framework Rhino Mocks ou qualquer outro que você já usa sem poluir sua modelagem com um emaranhado de coisas públicas sem a real necessidade.
Até mais!