Falso DbContext do Entity Framework 4.1 para teste

Estou usando este tutorial para Fake my DbContext e teste: http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic -repository/

Mas eu tenho que mudar a implementação do FakeMainModuleContext para usar em meus Controllers:

public class FakeQuestiona2011Context : IQuestiona2011Context { private IDbSet _credencial; private IDbSet _perfil; private IDbSet _apurador; private IDbSet _entrevistado; private IDbSet _setor; private IDbSet _secretaria; private IDbSet _pesquisa; private IDbSet _pergunta; private IDbSet _resposta; public IDbSet Credencial { get { return _credencial ?? (_credencial = new FakeDbSet()); } set { } } public IDbSet Perfil { get { return _perfil ?? (_perfil = new FakeDbSet()); } set { } } public IDbSet Apurador { get { return _apurador ?? (_apurador = new FakeDbSet()); } set { } } public IDbSet Entrevistado { get { return _entrevistado ?? (_entrevistado = new FakeDbSet()); } set { } } public IDbSet Setor { get { return _setor ?? (_setor = new FakeDbSet()); } set { } } public IDbSet Secretaria { get { return _secretaria ?? (_secretaria = new FakeDbSet()); } set { } } public IDbSet Pesquisa { get { return _pesquisa ?? (_pesquisa = new FakeDbSet()); } set { } } public IDbSet Pergunta { get { return _pergunta ?? (_pergunta = new FakeDbSet()); } set { } } public IDbSet Resposta { get { return _resposta ?? (_resposta = new FakeDbSet()); } set { } } public void SaveChanges() { // do nothing (probably set a variable as saved for testing) } } 

E meu teste assim:

 [TestMethod] public void IndexTest() { IQuestiona2011Context fakeContext = new FakeQuestiona2011Context(); var mockAuthenticationService = new Mock(); var apuradores = new List { new Apurador() { Matricula = "1234", Nome = "Acaz Souza Pereira", Email = "acaz@telecom.inf.br", Ramal = "1234" }, new Apurador() { Matricula = "4321", Nome = "Samla Souza Pereira", Email = "samla@telecom.inf.br", Ramal = "4321" }, new Apurador() { Matricula = "4213", Nome = "Valderli Souza Pereira", Email = "valderli@telecom.inf.br", Ramal = "4213" } }; apuradores.ForEach(apurador => fakeContext.Apurador.Add(apurador)); ApuradorController apuradorController = new ApuradorController(fakeContext, mockAuthenticationService.Object); ActionResult actionResult = apuradorController.Index(); Assert.IsNotNull(actionResult); Assert.IsInstanceOfType(actionResult, typeof(ViewResult)); ViewResult viewResult = (ViewResult)actionResult; Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(IndexViewModel)); IndexViewModel indexViewModel = (IndexViewModel)viewResult.ViewData.Model; Assert.AreEqual(3, indexViewModel.Apuradores.Count); } 

Estou fazendo certo?

Infelizmente você não está fazendo certo porque esse artigo está errado. Ele finge que o FakeContext tornará sua unidade de código testável, mas não fará isso. Uma vez que você exponha IDbSet ou IQueryable ao seu controller e você falsifica o conjunto na coleção de memory, você nunca pode ter certeza de que seu teste de unidade realmente testa seu código. É muito fácil escrever uma consulta LINQ no seu controlador que passará no teste de unidade (porque o FakeContext usa o LINQ-to-Objects), mas falha no tempo de execução (porque o seu contexto real usa o LINQ to Entities). Isso faz todo o propósito da sua unidade testar sem utilidade.

Minha opinião: Não se preocupe em falsificar o contexto se você quiser expor conjuntos ao controlador. Em vez disso, use testes de integração com database real para teste. Essa é a única maneira de validar que as consultas LINQ definidas no controlador fazem o que você espera.

Claro, se você quiser chamar apenas ToList ou FirstOrDefault em seus sets, seu FakeContext irá atendê-lo bem, mas quando você fizer algo mais complexo, você pode encontrar uma armadilha em breve (basta colocar a string “Não pode ser traduzido em uma expressão de loja” no Google – todos esses problemas aparecerão somente quando você executar Linq para entidades, mas eles passarão seus testes com Linq para objects).

Essa é uma pergunta bastante comum, então você pode verificar alguns outros exemplos:

  • Para retornar IQueryable ou não retornar IQueryable
  • Testes Unitários DbContext
  • ASP.NET MVC3 e primeira arquitetura do Code do Entity Framework
  • Organizacionalmente, onde devo colocar consultas comuns ao usar primeiro o Código do Entity Framework?
  • É possível stub contexto e classs do Entity Framework para testar a camada de access a dados?

“Infelizmente você não está fazendo certo porque o artigo está errado. Ele finge que o FakeContext tornará sua unidade de código testável, mas não”

Eu sou o criador da postagem do blog a que você se refere. O problema que vejo aqui é um mal-entendido dos fundamentos do teste de unidade N-Layered. Minha postagem não deve ser usada diretamente para testar a lógica do controlador.

Um teste de unidade deve fazer exatamente como o nome indica e testar ‘One Unit’. Se eu estou testando um controlador (como você está fazendo acima) eu esqueço tudo sobre o access a dados. Eu deveria estar removendo todas as chamadas para o contexto do database em minha mente e substituí-las por uma chamada de método de checkbox preta como se essas operações fossem desconhecidas para mim. É o código em torno dessas operações que estou interessado em testar.

Exemplo:

No meu aplicativo MVC, usamos o padrão de repository. Eu tenho um repository, diga CustomerRepository: ICustomerRepository, que executará todas as operações do meu database do cliente.

Se eu fosse testar meus controladores, eu desejaria que os testes testassem meu repository, meu access ao database e a própria lógica do controlador? claro que não! existem muitas ‘unidades’ neste pipeline. O que você quer fazer é criar um repository falso que implemente o ICustomerRepository para permitir que você teste a lógica do controlador isoladamente.

Isso, tanto quanto sei, não pode ser feito apenas no contexto do database. (exceto talvez por usar o Microsoft Moles, que você pode verificar se quiser). Isto é simplesmente porque todas as consultas são realizadas fora do contexto em sua class de controlador.

Se eu quisesse testar a lógica do CustomerRepository, como eu faria isso? A maneira mais fácil é usar um contexto falso. Isso me permitirá ter certeza de que, quando estou tentando obter um cliente por id, ele realmente obtém o cliente por id e assim por diante. Os methods de repository são muito simples e o problema “Não pode ser traduzido em uma expressão de repository” geralmente não será exibido. Embora em alguns casos pequenos possa (algumas vezes devido a consultas de linq incorretamente escritas) nesses casos, é importante também realizar testes de integração que testarão todo o seu código no database. Esses problemas serão encontrados no teste de integração. Eu usei essa técnica de N-Layered por um bom tempo agora e não encontrei nenhum problema com isso.

Testes de Integração

Obviamente, testar seu aplicativo em relação ao database em si é um exercício dispendioso e, quando você obtém dezenas de milhares de testes, ele se torna um pesadelo; por outro lado, ele imita melhor como o código será usado no “mundo real”. Esses testes são importantes (da interface do usuário para o database) também e serão executados como parte dos testes de integração, NÃO testes de unidade.

Acaz, o que você realmente precisa em seu cenário é um repository que é escarnecedor / fakeable. Se você deseja testar seus controladores como você está fazendo, então seu controlador deve estar recebendo um object que envolve a funcionalidade do database. Em seguida, ele pode retornar tudo o que você precisa para testar todos os aspectos da funcionalidade do seu controlador.

consulte http://msdn.microsoft.com/pt-br/library/ff714955.aspx

Para testar o próprio repository (debatido se necessário em todos os casos), você irá querer falsificar o contexto ou usar algo como o framework ‘Moles’.

O LINQ é inerentemente difícil de testar. O fato de a consulta ser definida fora do contexto usando methods de extensão nos dá uma grande flexibilidade, mas cria um pesadelo de teste. Envolva seu contexto em um repository e esse problema desaparece.

desculpa tanto tempo 🙂

Como Ladislav Mrnka mencionou, você deve testar o Linq-to-Entity, mas não o Linq-to-Object. Eu normalmente usei Sql CE como teste de database e sempre recriar o database antes de cada teste. Isso pode tornar o teste um pouco lento, mas até agora eu estou bem com o desempenho dos meus 100+ testes unitários.

Primeiro, altere a configuração da string de conexão com SqlCe no App.config do projeto de teste.

    

Em segundo lugar, defina o inicializador db com DropCreateDatabaseAlways .

 Database.SetInitializer(new DropCreateDatabaseAlways()); 

E então, force a EF a inicializar antes de executar cada teste.

 public void Setup() { Database.SetInitializer(new DropCreateDatabaseAlways()); context = new MyDbContext(); context.Database.Initialize(force: true); } 

Se você estiver usando xunit, chame o método Setup em seu construtor. Se você estiver usando o MSTest, coloque TestInitializeAttribute nesse método. Se nunit …….

Você pode criar um Fake DbContext usando o Effort for EF 6+. Veja https://effort.codeplex.com/ . Esforço significa Força de Trabalho de E ntidade F atura O bjectContext R ealization T ool.

Para um artigo com um exemplo de trabalho, consulte http://www.codeproject.com/Tips/1036630/Using-Effort-Entity-Framework-Unit-Testing-Tool ou http://www.codeproject.com/Articles/ 460175 / Duas estratégias para testar-Entidade-Estrutura-Esforço? Msg = 5122027 # xx5122027xx .

Eu sei que não devemos fazê-lo, mas às vezes você tem que fazê-lo de qualquer maneira (seu chefe pode pedir-lhe também, por exemplo, e não mudaria de idéia).

Então, como eu tive que fazer, deixo aqui para ajudar algumas pessoas. Eu sou muito novo para c # / .net e tudo isso está longe de ser otimizado / limpo, eu suponho, mas parece funcionar.

seguindo o MSDN Encontre aqui a class que está faltando e usando um pouco de reflection eu consegui adicionar propriedades unidirecionais: Os elementos chave aqui são o AddNavigationProperty e o RefreshNavigationProperties . Se alguém tiver sugestão para melhorar este código, eu vou aceitá-los com prazer

 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Linq.Expressions; namespace MockFactory { public class TestDbSet : DbSet, IQueryable, IEnumerable, IDbAsyncEnumerable where TEntity : class { public readonly ObservableCollection _data; private readonly IQueryable _query; private readonly Dictionary entities; public TestDbSet() { _data = new ObservableCollection(); _query = _data.AsQueryable(); entities = new Dictionary(); } public override ObservableCollection Local { get { return _data; } } IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() { return new TestDbAsyncEnumerator(_data.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } Type IQueryable.ElementType { get { return _query.ElementType; } } Expression IQueryable.Expression { get { return _query.Expression; } } IQueryProvider IQueryable.Provider { get { return new TestDbAsyncQueryProvider(_query.Provider); } } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } public void AddNavigationProperty(DbSet dbSet) where T : class { entities.Add(typeof (T), dbSet); } public void RefreshNavigationProperty(TEntity item) { foreach (var entity in entities) { var property = item.GetType().GetProperty(entity.Key.Name); var type = (int)item.GetType().GetProperty(entity.Key.Name.Replace(typeof(TEntity).Name, "")).GetValue(item); var dbSets = (IEnumerable)entity.Value.GetType().GetField("_data").GetValue(entity.Value); var dbSet = dbSets.Single(x => (int)x.GetType().GetProperty("Id").GetValue(x) == type); property.SetValue(item, dbSet); } } public override TEntity Add(TEntity item) { RefreshNavigationProperty(item); _data.Add(item); return item; } public override TEntity Remove(TEntity item) { _data.Remove(item); return item; } public override TEntity Attach(TEntity item) { _data.Add(item); return item; } public override TEntity Create() { return Activator.CreateInstance(); } public override TDerivedEntity Create() { return Activator.CreateInstance(); } } } 

Você pode então criar seu contexto

  public TestContext() { TypeUsers = new TestDbSet(); StatusUsers = new TestDbSet(); TypeUsers.Add(new TypeUser {Description = "FI", Id = 1}); TypeUsers.Add(new TypeUser {Description = "HR", Id = 2}); StatusUsers.Add(new StatusUser { Description = "Created", Id = 1 }); StatusUsers.Add(new StatusUser { Description = "Deleted", Id = 2 }); StatusUsers.Add(new StatusUser { Description = "PendingHR", Id = 3 }); Users = new TestDbSet(); ((TestDbSet) Users).AddNavigationProperty(StatusUsers); ((TestDbSet)Users).AddNavigationProperty(TypeUsers); } public override DbSet TypeUsers { get; set; } public override DbSet StatusUsers { get; set; } public override DbSet Users { get; set; } public int SaveChangesCount { get; private set; } public override int SaveChanges(string modifierId) { SaveChangesCount++; return 1; } } 

Finalmente, não se esqueça em seu teste para atualizar as propriedades de navegação antes de fazer o assert (deve haver uma maneira melhor, mas eu não consegui encontrá-lo)

 ContextFactory.Entity.Users.Each(((TestDbSet) ContextFactory.Entity.Users).RefreshNavigationProperty);