Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Testing DbContext in local methods

I have an ASP.NET MVC project, and am writing my service something along the lines of this:

public class UserService : IUserService
{
    public void AddUser(UserModel userModel)
    {
        if (ModelState.IsValid){
            var user = new User
            {
                FirstName = userModel.FirstName,
                LastName = userModel.LastName,
                Email = userModel.Email,
                Age = userModel.Age
            };
            using (var context = new MyDbContext())
            {
                context.Users.Add(user);
            }
        }
    }
    //... More code for service
}

And my DbContext class:

public class MyDbContext : DbContext 
{
    public MyDbContext(): base("name=mydb"){}

    public DbSet<User> Users {get; set;}

    //.. More domain objects.
}

This runs fine for the most part, but the problem arises when I want to try and test it. I'm following the general guidelines here with mocking it out, but that's only if the DbContext is declared as a class instance, as opposed to a functional variable. I want to do it this way because it enables me to have control over the transaction and context disposal. Anyone have any suggestions on how to do this?

My mocks are like this:

public class UserServiceTest 
{
    [Fact]
    public void TestAddUser()
    {
        var userSet = GetQueryableMockDbSet(userData.ToArray());
        var context = new MyDbContext
        {
            Users = userSet
        };
        var userService = new UserService();
        userService.AddUser(); // HOW DO I GET CONTEXT IN HERE?
    }   

    private static DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class
        {
            var queryable = sourceList.AsQueryable();

            var dbSet = new Mock<DbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
            dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());

            return dbSet.Object;
        }
}
like image 890
Ivan Peng Avatar asked Oct 20 '22 04:10

Ivan Peng


2 Answers

One way to accomplish your goal of

I want to do it this way because it enables me to have control over the transaction and context disposal.

Would be to do something along the lines of

public class UserService : IUserService {
   private ContextFactory _ContextFactory;

   public UserService(ContextFactory contextFactory) {
       _ContextFactory = contextFactory;
   }

   public void AddUser(UserModel userModel) {
       ...
       using (var context = _ContextFactory.CreateInstance()) {
           context.Users.Add(user);
       }
   }

}

This would allow you to inject different instances of ContextFactory which would create the DbContext using the Mock

like image 106
3dd Avatar answered Oct 28 '22 22:10

3dd


I would definetly prefer the IOC approach but If you want a quick solution without rewriting too much code you can do something like this:

public class UserService : IUserService 
{
    public static Func<MyDbContext> CreateContext = () => new MyDbContext();

    public UserService ()
    {
    } 

    public void AddUser(UserModel userModel)
    {
        ...
        using (var context = CreateContext())
        {
            context.Users.Add(user);
        }
    }
}

You don't have to change your production code but in your TestMethod you can do

    var context = new MyDbContext
    {
        Users = userSet
    };
    UserService.CreateContext = () => context;
like image 39
Jürgen Steinblock Avatar answered Oct 29 '22 00:10

Jürgen Steinblock