46

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?

Acaz Souza
  • 8,311
  • 11
  • 54
  • 97

5 Answers5

126

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:

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • @Ladislav There's no another solution to use a fake/mock/stub context of entity framework and do a good unit test's? – Acaz Souza Aug 02 '11 at 12:11
  • @Ladislav if I use repository pattern? is the only way? – Acaz Souza Aug 02 '11 at 12:15
  • @Acaz: Theoretically yes if you have fake linq provider simulating behavior of linq to entities. Practically I don't know about any implementation available. Linq is still only one problem there are other problems not related to Linq. – Ladislav Mrnka Aug 02 '11 at 12:17
  • 1
    It depends how you are going to use repository. – Ladislav Mrnka Aug 02 '11 at 12:18
  • @Ladislav a Generic repository saving, deleting in memory, repository that return IList. Repository. What you think? – Acaz Souza Aug 02 '11 at 12:40
  • There are not many things to test in repository since it is basically wrapper for dbcontext. But you still can test it. I would more concentrate on testing services/controllers/routes in unit test and test database operation in integration test. – Tae-Sung Shin Aug 02 '11 at 15:05
  • 13
    I agree with [Brent's answer](http://stackoverflow.com/a/8131121/376366). Faking DbSets in memory may not cover all possibilities, as well explained by Ladislav, but it's an effective technique for unit testing. I successfully applied the approach [described by Richard Garside](http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context). Integration testing will ensure end-to-end scenarios work. It's important to have in mind that unit testing and integration testing are complementary, not mutually exclusive alternatives. – Fernando Correia May 20 '12 at 00:22
  • 3
    The whole point of unit testing is to ONLY test that unit. Presumably you would be testing something like the business logic in a web service that takes a DTO, validates it, saves it into a database. By definition, you don't CARE about whether or not the object is actually persisted into the repository, because that could change later. You only care whether the business logic is correct: whether the correct calls were made on the repository, or (if the object fails validation) whether the correct errors are thrown, etc. – HiredMind Sep 25 '13 at 21:33
  • 1
    @HiredMind: I fully agree with that but at the end you also need test which will care about successful query execution or persistence. This all point to Single responsibility principle. If you want to test a business logic in your unit the unit should have single responsibility of performing that logic - not performing database queries (even defining them with LINQ). Once you have it done this way, your approach will work as a charm but if you place L2E queries directly to the same unit as business logic you have a trouble. You need integration test to fully test that queries and your unit. – Ladislav Mrnka Oct 02 '13 at 11:54
  • 3
    Would the best approach not to be to write unit tests with a fake DbSet that test your coded logic, AND to write integration tests that test that the combined solution really works when integrated? – Russell Horwood Apr 24 '14 at 10:43
  • 1
    @Russell: Yes. The bets way to do that is to refactor your code. Use single responsibility principle and consider the logic one responsibility and query execution another one. Than you have two different "systems under tests" which are composed together. In unit test code you are not faking the EF stuff itself but your method to execute the query and return enumeration (not queryable). That is the way I consider safe. It still uses principles from my answer - no faking of stuff I don't own (EF), no returning `IQueryable` and writting integration tests for query execution. – Ladislav Mrnka Apr 25 '14 at 08:58
65

"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 :)

refactorthis
  • 1,893
  • 15
  • 9
  • 1
    Just found this on the Microsoft ASP.NET MVC will probably explain much better than me. If confused take a look! [MSDN Unit Testing ASP.NET MVC](http://msdn.microsoft.com/en-us/magazine/dd942838.aspx) – refactorthis Nov 15 '11 at 03:33
  • 2
    I agree with this point of view. Richard Garside [describes](http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context) an approach for Entity Framework data context mocking that is very clean and works very well. – Fernando Correia May 20 '12 at 00:27
  • 2
    In order to test repositories you will want to use integration tests to verify your queries and persistence logic and unit tests to verify anything else. It really makes no sense to test queries with fake context. Why to write test with fake when you need to have test with a real provider anyway? – Ladislav Mrnka Jan 14 '13 at 22:31
  • 1
    I agree this appears to be a confusion of test types. I often see unit and integration tests overlap, which causes further confusion among the developers. I prefer to place integration and unit tests in separate test projects, to employ explicit segregation. This practice may help reduce confusion in your team (the reader not necessarily Acaz). – Mike Christian Mar 24 '13 at 18:48
23

As Ladislav Mrnka mentioned, you should test Linq-to-Entity but not Linq-to-Object. I normally used Sql CE as testing DB and always recreate the database before each test. This may make test a little bit slow but so far I'm OK with the performance for my 100+ unit tests.

First, change the connection string setting with SqlCe in the App.config of you test project.

<connectionStrings>
    <add name="MyDbContext"
       connectionString="Data Source=|DataDirectory|MyDb.sdf"
         providerName="System.Data.SqlServerCe.4.0"
         />
</connectionStrings>

Second, set the db initializer with DropCreateDatabaseAlways.

Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());

And Then, force EF to initialize before running each test.

public void Setup() {
    Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());

    context = new MyDbContext();
    context.Database.Initialize(force: true);
}

If you are using xunit, call Setup method in your constructor. If you are using MSTest, put TestInitializeAttribute on that method. If nunit.......

Soe Moe
  • 3,428
  • 1
  • 23
  • 32
  • This saved me so much time and effort (and maintenance.. and saved me having to inject my context). Definitely the best way to do it for any small/medium project. – Brian Rosamilia Nov 19 '11 at 08:27
  • Do all queries that run against SQL Server also run against SQL Server Ce? – Piotr Perak Aug 19 '12 at 22:12
  • Why are you calling `Database.Initialize()` when a `DropCreateDatabaseAlways` initializer always initializes anyway? – ProfK Aug 30 '12 at 11:38
  • @ProftK hmm.. can't remember exactly now. :). But I think it is because I need to call Initialize with `force: true`, otherwise it won't work. Pls let me know, you can manage to make it work without `Initialize` call, I'll update the answer so that others can get better answer. Thx. – Soe Moe Sep 07 '12 at 03:25
  • @SoeMoe instead of recreating the database what takes time I would use the Rollback attribute of xUnit for every test. Saves much time. – Elisabeth Feb 19 '13 at 20:13
  • 1
    @Elisa yeah, it would be better. :) For other test frameworks, I'm now creating database only in assembly initialize and using transaction scope to each test. Must faster. :) – Soe Moe Mar 01 '13 at 06:53
  • 2
    This is what I ended up doing... after many many hours of experimenting. I found that faking the DbContext (ultimately faking IDbSet) was not worth the effort. THIS is the only REAL way to test a repository, against a real database (localdb in my case). This sort of blurs the the line between a Unit test and an Integration test, because although you're unit testing your repo, its still testing the the lower layers (the real context) and such as an integration test would. Still, as far as Im concerned its a "Unit" test of my repo, the tests from my controllers would be the integration tests. – Brandon Mar 23 '14 at 14:13
1

You can create a Fake DbContext by using Effort for EF 6+. See https://effort.codeplex.com/. Effort stands for Entity Framework Fake ObjectContext Realization Tool.

For an article with a working sample, please see http://www.codeproject.com/Tips/1036630/Using-Effort-Entity-Framework-Unit-Testing-Tool or http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx.

user8128167
  • 6,929
  • 6
  • 66
  • 79
0

I know we shouldn't do it but sometimes you have to anyway (your boss might ask you too for instance and wouldn't change his mind).

So as I had to do it I leave it here it might help some people. I'm quite new to c# / .net and all so it's far from being optimized/clean I suppose but it seems to work.

following MSDN Find here the missing class and using a bit of reflection I managed to add one way properties : Key elements here are the AddNavigationProperty and RefreshNavigationProperties. If anyone has suggestion to improve this code I'll gladly take them

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<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity>
        where TEntity : class
    {
        public readonly ObservableCollection<TEntity> _data;
        private readonly IQueryable _query;
        private readonly Dictionary<Type, object> entities;

        public TestDbSet()
        {
            _data = new ObservableCollection<TEntity>();
            _query = _data.AsQueryable();

            entities = new Dictionary<Type, object>();
        }

        public override ObservableCollection<TEntity> Local
        {
            get { return _data; }
        }

        IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
        {
            return new TestDbAsyncEnumerator<TEntity>(_data.GetEnumerator());
        }

        IEnumerator<TEntity> IEnumerable<TEntity>.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<TEntity>(_query.Provider); }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        public void AddNavigationProperty<T>(DbSet<T> 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<object>)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<TEntity>();
        }

        public override TDerivedEntity Create<TDerivedEntity>()
        {
            return Activator.CreateInstance<TDerivedEntity>();
        }
    }
}

You can then create your context

 public TestContext()
        {
            TypeUsers = new TestDbSet<TypeUser>();
            StatusUsers = new TestDbSet<StatusUser>();

            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<User>();

            ((TestDbSet<User>) Users).AddNavigationProperty(StatusUsers);
           ((TestDbSet<User>)Users).AddNavigationProperty(TypeUsers);

        }

        public override DbSet<TypeUser> TypeUsers { get; set; }
        public override DbSet<StatusUser> StatusUsers { get; set; }
        public override DbSet<User> Users { get; set; }
        public int SaveChangesCount { get; private set; }

        public override int SaveChanges(string modifierId)
        {
            SaveChangesCount++;
            return 1;
        }
    }

Finally do not forget in your test to refresh the navigation properties before doing the assert (there should be a better way but I couldn't find it )

ContextFactory.Entity.Users.Each(((TestDbSet<User>) ContextFactory.Entity.Users).RefreshNavigationProperty);
Lomithrani
  • 2,033
  • 3
  • 18
  • 24