Search code examples
entity-frameworkunit-testingentity-framework-6dbcontextusing

How do I make DbContext in using statement work with unit tests?


The situation I have is this:

  1. I am working on an ASP.NET MVC project that does not implement the repository pattern
  2. All DbContext calls are made from the controller layer
  3. DbContext is wrapped in a using statement and instantiated as necessary for memory management purposes NOT connection pool purposes
  4. We need to unit test these controllers, which means injecting the DbContext into the controller.
  5. We are not using an IoC container. I would have to create a new instance in a parameter-less constructor and use that for the life of the call to the controller's action.
  6. I cannot change most of these conditions.

Conditions I can change:

  1. Get rid of the using statement, if a reliable option can take its place.
  2. Admit this code is just not testable as-is and move on to spending energy elsewhere.

So, my question is this: how do I pass in a DbContext to the constructor so I can mock the responses being returned, but maintain the reliability of the using statement, as these tend to contradict each other.

I'm ok if I need to stop using the using statement, as long as there is a way to ensure that the context is disposed every time when it is done performing the requested action.

Any thoughts on this one?


Solution

  • Constructing a DbContext inside a method you'd like to test is no different than instantiating any other concrete instance of a dependency within the code, it means you cannot test with a mocked dependency. So the immediate options that come to mind are (without code/structure changes):

    • Implement an in-memory data provider to point EF at for the unit tests.
    • Set up a known-state database that can be restored between test runs. (More of an integration test, not well suited to TDD where you want tests that can run very quickly, very often.)
    • Put it in the "wasn't designed for unit testing" bucket due to lack of DI/IoC

    Mocking a DbContext is messy, but do-able. Once you have one mock-able I would recommend adding an IoC container like Autofac into the project. I don't know the circumstances that would prevent you from introducing an IoC container, but if the team is worried about it being an all-or-nothing re-factor and too big of a job, then I'd reassure them that it can be added with minimal changes to the project and in a way that does not break existing code. Aside from the DbContext, if the code isn't using DI/IoC Container, how do you plan to handle other concrete dependencies? You don't have to switch out all dependencies/controllers in one go, but improve them incrementally.

    Once a container is set up to resolve MVC Controllers, existing controllers with default constructors will not be affected. You can then register your DbContext with the container, and adjust your controller under test to accept the context in the constructor. The IoC Container would set up the DbContext lifetime scope to Instance per Request for example, so you don't need the using {} block. From there your tests can provide the mocked DbContext, while The container manages the Context lifespan.

    Regarding making unit-test friendly controllers/code with an IoC container I'd recently posted an article on the use of Lazy dependencies /w Autofac to make writing tests for classes with multiple dependencies a snap. You can have a read at https://medium.com/@StevePy/writing-easily-testable-code-with-autofac-lazy-properties-f9c63457c8ce