Alex K
Alex K

Reputation: 197

How to test methods where DBContext is created?

I don't have a lot of experience with unit testing. For example I have simple method in the application:

public void GetName()
    {
        UserRights rights = new UserRights(new DatabaseContext());
        string test = rights.LookupNameByID("12345");
        Console.WriteLine(test);
    }

In this case I'm able to test all methods of UserRights class by passing mocked DatabaseContext, but how can I test GetName() method? What is the best practice? Where DatabaseContext should be created?

Upvotes: 1

Views: 492

Answers (2)

Stephen Byrne
Stephen Byrne

Reputation: 7485

If you want to test the GetName method in a properly isolated way (i.e. a Unit Test) then you can't use new to create the UserRights instance in the method itself; because a test is really just another client of the code, therefore it can't (and shouldn't) know anything about how GetName works internally

So what this means is that for a proper Unit Test you must be able to replace all dependencies of a method with ones that the client fully controls - in the case, the code in the Unit Test is the client.

In your posted code, the client code has no control at all over UserRights nor DatabaseContext, so this is the first thing that has to change.

You need to rework your code so that the UserRights implementation can be supplied by the client. In fact once that's done, the problem about where DatabaseContext comes from is actually irrelevant for the Unit test because it doesn't care about how UserRights itself performs its tasks!

There are a number of ways of doing this; you can use Mocks or Stubs, you can use Constructor or Method injection, you could use a UserRights factory. Here is an example of using very simple stubs which IMHO is the best way to start and avoids having to learn a Mocking framework -- I personally would use Mocks for this but that's cos I am lazy :)

(code below assumes the class containing GetName is called "UserService", and uses xUnit framework; but MSTest will work just fine too)

Let's suppose you have control over the code of UserService so you can make the LookupNameByID method virtual (if you can't, then you may have to go the route if interfaces and mocks)

public class UserRights
{
    public virtual LookupNameByID(string id)
    {
        //does whatever a UserRights does.
    }
}

public class UserService
{
    readonly UserRights _rights;
    public UserService(UserRights rights)
    {
       _rights=rights; //null guard omitted for brevity
    }

    public string GetName(string id)
    {
       return _rights.LookupNameByID(id);
    }
}

now in your unit test code suppose you create a sub-class of UserRights like this:

public class ExplodingUserRights: UserRights
{
     public override string LookupNameByID(string id)
     {
         throw new Exception("BOOM!");
     }
}

Now you can write a test to see how GetName reacts when something bad happens:

[Fact]
public void LookupNameByID_WhenUserRightsThrowsException_DoesNotReThrow()
{
   //this test will fail if an exception is thrown, thus proving that GetName doesn't handle exceptions correctly.
   var sut = new UserService(new ExplodingUserRights()); <-Look, no DatabaseContext!
   sut.GetName("12345");
}

and one for when good things happen:

public class HappyUserRights: UserRights
{
     public override string LookupNameByID(string id)
     {
         return "yay!";
     }
}

[Fact]
public void LookupNameByID_ReturnsResultOfUserRightsCall()
{
   //this test will fail if an exception is thrown, thus proving that GetName doesn't handle exceptions correctly.
   var sut = new UserService(new HappyUserRights()); 
   var actual = sut.GetName("12345");
   Assert.Equal("yay!",actual);
}

and so on. Note that we never went anywhere near DatabaseContext, because that's a problem you only have to solve when you unit test the UserRights class itself. (and at that point I would probably recommend using Jeroen's advice from his linked article,and do an integration test, unless setup of a database for each test is something you can't or won't do, in which case you need to use interfaces and mocks)

Hope that helps.

Upvotes: 1

mknopf
mknopf

Reputation: 380

Separating your codes reliance on DB Context is something you will want to investigate, this can be accomplished using the Repository pattern. Since you're passing the DB Context into your objects constructor, in this case UserRights, you can pretty easily change your code to take in an Interface (or simply add an overloaded contructor to the class that accepts an interface, then call that in your unit tests, preserving any existing code). There are lots of ways to get this done, a quick Google search yielded the following article:

Once your class can accept an interface rather than (or as an alternative to) the strongly typed DB Context object you can use a Mock framework to assist with testing. Take a look at Moq for examples on how to use these in your unit tests https://github.com/Moq/moq4/wiki/Quickstart

Be Aware: Moq can become difficult to work with when your tests require data that is related via foreign keys, the syntax can be complicated to get your head around. One thing to watch out for is the temptation to make changes to your code just to make a unit test easier to set up, while there may be value in making this change it also may indicate that you need to re-think your unit test rather than violate your applications architecture and/or design patterns

Upvotes: 0

Related Questions