Entity Framework 4.1 Fake DbContext for Testing

.net asp.net-mvc entity-framework tdd unit-testing

Question

I'm using this tutorial to Fake my DbContext and test: http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic-repository/

But i have to change the FakeMainModuleContext implementation to use in my Controllers:

public class FakeQuestiona2011Context : IQuestiona2011Context
{
    private IDbSet<Credencial> _credencial;
    private IDbSet<Perfil> _perfil;
    private IDbSet<Apurador> _apurador;
    private IDbSet<Entrevistado> _entrevistado;
    private IDbSet<Setor> _setor;
    private IDbSet<Secretaria> _secretaria;
    private IDbSet<Pesquisa> _pesquisa;
    private IDbSet<Pergunta> _pergunta;
    private IDbSet<Resposta> _resposta;

    public IDbSet<Credencial> Credencial { get { return _credencial ?? (_credencial = new FakeDbSet<Credencial>()); } set { } }
    public IDbSet<Perfil> Perfil { get { return _perfil ?? (_perfil = new FakeDbSet<Perfil>()); } set { } }
    public IDbSet<Apurador> Apurador { get { return _apurador ?? (_apurador = new FakeDbSet<Apurador>()); } set { } }
    public IDbSet<Entrevistado> Entrevistado { get { return _entrevistado ?? (_entrevistado = new FakeDbSet<Entrevistado>()); } set { } }
    public IDbSet<Setor> Setor { get { return _setor ?? (_setor = new FakeDbSet<Setor>()); } set { } }
    public IDbSet<Secretaria> Secretaria { get { return _secretaria ?? (_secretaria = new FakeDbSet<Secretaria>()); } set { } }
    public IDbSet<Pesquisa> Pesquisa { get { return _pesquisa ?? (_pesquisa = new FakeDbSet<Pesquisa>()); } set { } }
    public IDbSet<Pergunta> Pergunta { get { return _pergunta ?? (_pergunta = new FakeDbSet<Pergunta>()); } set { } }
    public IDbSet<Resposta> Resposta { get { return _resposta ?? (_resposta = new FakeDbSet<Resposta>()); } set { } }

    public void SaveChanges()
    {
        // do nothing (probably set a variable as saved for testing)
    }
}

And my test like that:

[TestMethod]
public void IndexTest()
{
    IQuestiona2011Context fakeContext = new FakeQuestiona2011Context();
    var mockAuthenticationService = new Mock<IAuthenticationService>();

    var apuradores = new List<Apurador>
    {
        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);
}

I'm doing it right?

1
46
8/1/2011 8:36:32 PM

Accepted Answer

Unfortunately you are not doing it right because that article is wrong. It pretends that FakeContext will make your code unit testable but it will not. Once you expose IDbSet or IQueryable to your controller and you fake the set with in memory collection you can never be sure that your unit test really tests your code. It is very easy to write a LINQ query in your controller which will pass your unit test (because FakeContext uses LINQ-to-Objects) but fails at runtime (because your real context uses LINQ-to-Entities). That makes whole purpose of your unit testing useless.

My opinion: Don't bother with faking context if you want to expose sets to controller. Instead use integration tests with real database for testing. That is the only way how to validate that LINQ queries defined in controller do what you expect.

Sure, if you want to call just ToList or FirstOrDefault on your sets your FakeContext will serve you well but once you do anything more complex you can find a trap pretty soon (just put the string "Cannot be translated into a store expression" into Google - all these problems will appear only when you run Linq-to-entities but they will pass your tests with Linq-to-objects).

This is quite common question so you can check some other examples:

123
5/23/2017 12:10:13 PM

Popular Answer

"Unfortunately you are not doing it right because that article is wrong. It pretends that FakeContext will make your code unit testable but it will not"

I am the creator of the blog post that you refer to. The problem I see here is a misunderstanding of the fundamentals of N-Layered unit testing. My post is not meant to be used directly to test controller logic.

A unit test should do exactly as the name implies and test 'One Unit'. If I am testing a controller (as you are doing above) I forget all about the data access. I should be removing all of the calls to database context in my mind and replacing them with a black box method call as if those operations were unknown to me. It is the code around those operations that I am interested in testing.

Example:

In my MVC application we use the repository pattern. I have a repository, say CustomerRepository : ICustomerRepository, which will perform all of my Customer database operations.

If I were to test my controllers would I want the tests to test my repository, my database access, and the controller logic itself? of course not! there are many 'units' in this pipeline. What you want to do is create a fake repository which implements ICustomerRepository to allow you to test the controller logic in isolation.

This to the best of my knowledge cannot be done on the database context alone. (except maybe for using Microsoft Moles, which you can check out if you want). This is simply because all queries are performed outside of the context in your controller class.

If I wanted to test the CustomerRepository logic how would I do that? The easiest way is to use a fake context. This will allow me to make sure that when I'm trying to get a customer by id, it actually gets the customer by id and so forth. Repository methods are very simple and the "Cannot be translated into a store expression" problem will not usually surface. Though in some minor cases it may (sometimes due to incorrectly written linq queries) in these cases it is important to also perform integration tests that will test your code all the way through to the database. These problems will be found in integration testing. I have used this N-Layered technique for quite a while now and have found no problems with this.

Integration Tests

Obviously testing your app against the database itself is a costly exercise and once you get tens of thousands of tests it becomes a nightmare, on the other hand it best mimics how the code will be used in the 'real world'. These tests are important (from the ui to the database) also and they will be performed as part of the integration tests, NOT unit tests.

Acaz, what you really need in your scenario is a repository which is mockable/fakeable. If you wish to test your controllers as you are doing then your controller should be taking in an object which wraps the database functionality. Then it can return anything you need it to in order to test all aspects of your controller's functionality.

see http://msdn.microsoft.com/en-us/library/ff714955.aspx

In order to test the repository itself (debated if needed in all cases) you will want to either fake the context or use something along the lines of the 'Moles' framework.

LINQ is inherently hard to test. The fact that the query is defined outside of the context using extension methods gives us great flexibility but creates a testing nightmare. Wrap your context in a repository and this problem goes away.

sorry so long :)



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow