Jeff Dege
Jeff Dege

Reputation: 11720

How to mock an object that does a complicated entity framework LINQ query?

I'm trying to write a unit test for a simple mvc controller that makes a complicated LINQ query into the database:

public class HomeController
{
    private readonly DamagesDbContext db;

    public HomeController(DamagesDbContext db)
    {
        this.db = db;
    }

    // GET: /Home/
    [Authorize]
    public ActionResult Index()
    {
        var dashData = (from inc_c in db.incident_content
                       join inc in db.incidents
                       on inc_c.incidentid equals inc.incidentid
                       where inc.currentrevisionnumber == inc_c.revisionnumber
                       group inc_c by 1 into g
                       select new{
                           total = g.Count(),
                           open = g.Count(q => q.incidentstatus == "OPEN"),
                           closed = g.Count(q => q.incidentstatus == "CLOSED")
                       }).SingleOrDefault();

        ViewBag.total = dashData.total;
        ViewBag.open = dashData.open;
        ViewBag.closed = dashData.closed;            

        return View();
    }
}

And then in my test, I have:

var mockDb = new Mock<DamagesDbContext>();
mockDb.Setup(/* What goes here? */);

var homeController = new HomeController(mockDb.Object);

var result = homeController.Index();

// Various asserts go here...

But what do I do in Setup(), to wire up a replacement for that complicated LINQ query?

How do I know what actual methods are being called? Or what their arguments are?

==== EDITED ====

I think that part of my problem is that the LINQ expression, while kinda neat, doesn't make it clear what methods are being called on what objects.

I'm playing around with Resharper, for the first time, and I just noticed that it has a "convert LINQ to method chain" option. With that done, the above LINQ expression turns into:

var dashData = (this.db.incident_content.Join(this.db.incidents, inc_c => inc_c.incidentid,
    inc => inc.incidentid, (inc_c, inc) => new {inc_c, inc})
    .Where(@t => @t.inc.currentrevisionnumber == @t.inc_c.revisionnumber)
    .GroupBy(@t => 1, @t => @t.inc_c)
    .Select(g => new
    {
        total = g.Count(),
        open = g.Count(q => q.incidentstatus == "OPEN"),
        closed = g.Count(q => q.incidentstatus == "CLOSED")
    })).SingleOrDefault();

Which might make it a bit clearer as to what objects and methods need to be mocked.

Upvotes: 1

Views: 3961

Answers (5)

Void
Void

Reputation: 265

From what I can see alot of people have given you good advice but not answered the question.

From the query I can see you need to Mock two DbSets (b.incident_content and db.incidents) and add them to a FakeDbContext. Then wherever you eventially put the query you can test it using the fake context.

You can fake it as here: http://msdn.microsoft.com/en-us/data/dn314431.aspx

Upvotes: 1

Venu b
Venu b

Reputation: 428

Please decouple the db access logic from your controller
Inject the Db into the controller

So now you l have to test two components One is the db service
Other is the Controller service

Note when public ActionResult Index() is called it invokes the some DBService API to retrieve some resultSet
You l need to include tests to assert that
* Index is actually calling the RIGHT api on the db in various scenarios * Uses the resultset that is given back by the API and uses the same resultset to viewBag.TOTAL, viewBagOPEN etc

This reduces our controllers test to
1> Mock the DBService(eg using Moq)
2> Setup the mock DBService to expect a call to DBService.API
3> When the call does happen from controller to mockObject return back a prepared resultSet
4> Assert that the test on Index called the DBService.API else fail
5> Assert that when the call did happen and the pre-prepared resultset was returned, the output of Index is using the same.

The separated DBService, like you would imagine will have a public API
eg public ResultSet GetAllEntitiesThatAreOpenOrClosed(someparameters here) They would need tests on their own.
Again understand what the DBService class responsibility is and test them too.

Upvotes: 1

Oleksandr Kobylianskyi
Oleksandr Kobylianskyi

Reputation: 3380

It's not a good practice for big applications to use DbContext inside controllers, but anyway somewhere it will appear and this place should be covered with unit tests too. So...

  1. It's better to extract IDamagesDbContext interface and inject it instead of DamagesDbContext class.
  2. You should use IDbSet<T> interface not DbSet<T> class in your context properties that expose entities.
  3. You should write your own implementation of IDbSet<T>, which simulates in-memory database (you can use already written implementation with NuGet package FakeDbSet).
  4. After everything above is done you just fill your in-memory DB sets with appropriate data to achieve desirable test case.

Upvotes: 1

earthling42
earthling42

Reputation: 976

What I would do in this situation is to first run the code that you have and once you have information in your dashData variable serialize that to a file.

Next step, do as Colin suggests, split off your db code into a separate business layer object.

When you come to the bit where you want to mock out the business layer you get the mock to deserialize your file and send the captured output to your controller.

Because you know exactly what the db-'result' will be you can now test that your controller responds correctly to this set of data.

In this way you have isolated the controller and your unit tests will always only be testing the controllers functionality.

If different database results need to be handled very differently then you can repeat this process to cover all the different scenarios that you anticipate.

To make sure that your database is returning the right results requires unit-testing on the database side, which is something I am still struggling with.

A final word, my experience with unit testing (and tdd) so far is that I end up with a handful of classes which are individually fairly trivial to unit test, rather than two or three big classes. The good news is that my bug-rate has come down dramatically on new code and because I can now do regression testing I very rarely introduce new bugs into old code. I'm still working on getting a grip on integration testing, but I hope for a similar improvement from that.

Upvotes: 0

Colin Mackay
Colin Mackay

Reputation: 19175

Don't put database code in your controllers. That would be a good start to unit testing this. Move your database code to a separate class whose function is to query the database, then you can mock the call to that class.

Upvotes: 5

Related Questions