简体   繁体   中英

Unit testing database using EF

How does one unit test a service which connects to a database?

I have a playerRepository class in data access layer, which interacts with the database directly, and a playerService class in business layer which creates an instance of playerRepository and services random stuff like - deleting player, saving player, getting all players, getting the player by id/name yadda yadda.

I want to unit test the playerService without using the real database, but using in-memory database provided with EF.

The problem is, I'm having trouble figuring out how to set it up.

I have a PlayerContext : DbContext class, which is used as a model to code-first (done this part via tutorial on EF). And I have to add a parameter to constructor DbContextOptionsBuilder<PlayerContext> , but I don't know where to begin. I don't know how and where to setup the connection string, where does the "default" database store itself.

I want to do this by using EF without NSubstitute or Moq, I'm doing this to learn how it's done without using other frameworks except for EF.

public class PlayerContext : DbContext
{
    public PlayerContext() : base()
    {

    }

    public DbSet<Player> Players { get; set; }
}

public class Player
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int DciNumber { get; set; }
}

I'm using visual studio provided unit testing

[TestClass]
public class PlayerServiceTest
{
    // Should get all the players from database
    [TestMethod]
    public void GetAllPlayers()
    {
        // arrange - act
        // TODO: Return all the players from the database

        // assert
        // TODO: Should return empty list
    }

And the PlayerService class looks something like this

public class PlayerService
{
    private PlayerRepository _playerRepository = new PlayerRepository();


    public List<Player> GetAllPlayers()
    {
        var players = _playerRepository.GetAllPlayers();

        return players;
    }

PlayerRepository

public class PlayerRepository
{
    public List<Player> GetAllPlayers()
    {
        using (var context = new PlayerContext())
        {
            var players = context.Players.ToList();

            return players;
        }
    }

Generally my questions are:

  1. How to make the PlayerContext with another connection string which connects to in-memory database in case of unit test, and also how to provide it with the correct connection string when not running via unit-tests

  2. How to change the location of the database, because it's using a default path in C:\\Users .

I'm not looking for integration tests with the DAL's PlayerRepository , I just want to test the business layer and all I need is, when I run the tests that the PlayerService to uses the PlayerRepository which connects to in-memory database and that's it. Otherwise it should connect to the local db stored in the same folder as the main exe for the app

Help needed!

The missing piece is dependency injection / IoC. The principle here is to define your dependencies (Repository) with a contract interface that can be mocked out. Inject that dependency into the classes that depend on it. This way you can substitute out concrete implementations like databases, file handling, etc. with mock up objects that return a known state, throw an expected exception, etc. to test your business logic's handling of those scenarios.

public class PlayerService
{
    private readonly IPlayerRepository _playerRepository = null;

    public PlayerService(IPlayerRepository playerRepository)
    {
         _playerRepository = playerRepository ?? throw new ArgumentNullException("playerRepository");
    }


    public List<Player> GetAllPlayers()
    {
        var players = _playerRepository.GetAllPlayers();

        return players;
    }
}

Have a look at IoC containers such as Autofac, Unity, Ninject, etc. for examples of how a container can be st up to automatically identify and inject concrete class instances into your services when it constructs them.

When you go to write a unit test you create a mock of the IPlayerRepository class (see: Moq for example) and pass that to your service under test. Ie:

[Test]
public void TestService()
{
   var mockRepository = new Mock<IPlayerRepository>();
   mockRepository.Setup(x => x.GetPlayers()).Returns(buildTestPlayerList());
   var serviceUnderTest = new PlayerService(mockRepository.Object);
   // continue with test...
}

At a worst case: if you want to forgo the container, this can work as well:

public class PlayerService
{
    private IPlayerRepository _playerRepository = null;
    public IPlayerRepository PlayerRepository
    {
        get { return _playerRepository ?? (_playerRepository = new PlayerRepository()); }
        set { _playerRepository = value; }
    }

    // ...
}

... and then with the test...

[Test]
public void TestService()
{
   var mockRepository = new Mock<IPlayerRepository>();
   mockRepository.Setup(x => x.GetPlayers()).Returns(buildTestPlayerList());
   var serviceUnderTest = new PlayerService { PlayerRepository = mockRepository.Object };
   // continue with test...
}

This is a pattern I call "lazy property injection" where you can opt to send a dependency in, but by default it will simply create the default dependency. This can be a useful pattern when introducing dependency substitution & unit testing into legacy code that was relying heavily on newing up classes mid-code. It's lazy in the sense that the dependency is only "newed" the first time it is accessed. If you call a method in the service that doesn't need the dependency then there is no initialization of every dependency, only the ones that are used. I highly recommend reading up on IoC containers though since they help automate wiring up dependencies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM