Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking DbSet.AddOrUpdate

I am following the instructions here to try to mock my DbSet and DbContext for unit testing using Moq.

The service that I'm testing looks like this

public class MyItemService
{
   private MyContext context;

   public void AddItem(MyItem item)
   {
      this.context.MyItems.AddOrUpdate(item);
      this.context.SaveChanges();
   }
}

My unit test looks like this

[TestMethod]
public void AddItem_ShouldSucceed()
{
    var myItems = new Mock<DbSet<MyItem>>();

    var context = new Mock<MyContext>();
    context.Setup(e => e.MyItems).Returns(myItems.Object);          

    MyItemService service = new MyItemService(context.Object);

    service.AddItem(new MyItem
    {
        Id = "1234"
    });
}

When I run the test, I am getting the exception

System.InvalidOperationException: Unable to call public, instance method AddOrUpdate on derived IDbSet<T> type 'Castle.Proxies.DbSet``1Proxy'. Method not found.

I assume the problem is because AddOrUpdate is an extension method on DbSet. I do have System.Data.Entity.Migrations included in my test .cs file.

I tried adding the line

myItems.Setup(e => e.AddOrUpdate(It.IsAny<MyItem>()));

to my unit test, but then I get the exception

System.NotSupportedException: Expression references a method that does not belong to the mocked object: e => e.AddOrUpdate(new[] { It.IsAny() })

Is there any way that I can have my unit test work when my method being tested is using AddOrUpdate?

like image 588
Ben Rubin Avatar asked Dec 19 '22 07:12

Ben Rubin


2 Answers

I came across this old question after searching for similar issue.

All we need to do is to create MockableDbSetWithExtensions abstract class, and use it instead of DbSet, whenever you want to test a method which calls AddOrUpdate.

It is how Entity Framework team tests their code too.

public abstract class MockableDbSetWithExtensions<T> : DbSet<T>
    where T : class
{
    public abstract void AddOrUpdate(params T[] entities);
    public abstract void AddOrUpdate(Expression<Func<T, object>> 
         identifierExpression, params T[] entities);
}

Usage

[TestMethod]
public void AddItem_ShouldSucceed()
{
    var myItems = new Mock<MockableDbSetWithExtensions<MyItem>>();

    var context = new Mock<MyContext>();
    context.Setup(e => e.MyItems).Returns(myItems.Object);

    MyItemService service = new MyItemService(context.Object);

    ...
}
like image 132
Win Avatar answered Dec 24 '22 01:12

Win


As Nkosi said in the comments you add wrapper around the AddOrUpdate. You can implement the wrapper like this:

public class MyDbContext: DbContext 
{
    public virtual DbSet<MyItem> Items {get; set;}

    public virtual AddOrUpdate<T>(T item)
    {
         if(typeof(T) == typeof(MyItem))
         {
             Items.AddOrUpdate(item as MyItem)
         }
    }
}

And then call it from your class like:

public class MyItemService
{
   private MyContext context;

   public void AddItem(MyItem item)
   {
      this.context.AddOrUpdate(item);
      this.context.SaveChanges();
   }
}
like image 21
NavidM Avatar answered Dec 24 '22 00:12

NavidM