简体   繁体   English

模拟 DbSet 不返回对象

[英]Mocked DbSet not returning an object

I am trying to test an update function by mocking the data using Moq.我正在尝试通过使用 Moq 模拟数据来测试更新功能。 I am using Entity Framework 6.我正在使用实体框架 6。

I can print out a count of the DbSet and it is the expected amount.我可以打印出DbSet的计数,这是预期的数量。 However, when it tries to select an object, it throws an exception, NullReferenceException: Object reference not set to an instance of an object.但是,当它尝试选择一个对象时,它会抛出一个异常NullReferenceException: Object reference not set to an instance of an object.

Here is my test class which sets up the mocked DbSets and DbContext这是我的测试类,它设置了DbSetsDbContext

[TestFixture]
public class ProductControllerTest
{
    private ProductController controller;
    private IProductRepository productRepo;
    private IUnitOfWork unitOfWork;
    private IBrandRepository brandRepo;
    private ICategoryRepository categoryRepo;
    private ISegmentRepository segmentRepo;
    private ITypeRepository typeRepo;
    private IEnumerable<Product> productList;

    [SetUp]
    public void Init()
    {
        IEnumerable<Brand> brandList = new List<Brand>{
            new Brand{
                Id = 1,
                Name = "Unknown"
            },
            new Brand{
                Id = 2,
                Name = "Clorox"
            },
            new Brand{
                Id = 3,
                Name = "Glad"
            }
        };
        var brandData = brandList.AsQueryable();

        productList = new List<Product>{
            new Product{
                Id = "0000000001",
                ParentAsin = "0000000010",
                Title = "Mocked Product #1",
                ReleaseDate = DateTime.Now,
                BrandId = 1,
                CategoryId = 1,
                SegmentId = 1,
                TypeId = 1,
                Brand = brandList.ElementAt(0)
            }, 
            new Product{
                Id = "0000000002",
                ParentAsin = "0000000010",
                Title = "Mocked Product #2",
                ReleaseDate = DateTime.Now,
                BrandId = 1,
                CategoryId = 1,
                SegmentId = 1,
                TypeId = 1,
                Brand = brandList.ElementAt(0)
            },
            new Product{
                Id = "0000000003",
                ParentAsin = "0000000010",
                Title = "Mocked Product #3",
                ReleaseDate = DateTime.Now,
                BrandId = 2,
                CategoryId = 3,
                SegmentId = 3,
                TypeId = 2,
                Brand = brandList.ElementAt(1)
            }
        };
        var productData = productList.AsQueryable();

        brandList.ElementAt(1).Products.Add(productList.ElementAt<Product>(2));

        var mockProductSet = new Mock<DbSet<Product>>();
        mockProductSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(productData.Provider);
        mockProductSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(productData.Expression);
        mockProductSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(productData.ElementType);
        mockProductSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(productData.GetEnumerator());

        var mockBrandSet = new Mock<DbSet<Brand>>();
        mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.Provider).Returns(brandData.Provider);
        mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.Expression).Returns(brandData.Expression);
        mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.ElementType).Returns(brandData.ElementType);
        mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.GetEnumerator()).Returns(brandData.GetEnumerator());

        var mockContext = new Mock<ApplicationDbContext>() { CallBase = true };
        mockContext.Setup(m => m.Set<Product>()).Returns(mockProductSet.Object);
        mockContext.Setup(m => m.Set<Brand>()).Returns(mockBrandSet.Object);

        unitOfWork = new UnitOfWork(mockContext.Object);
        brandRepo = new BrandRepository(mockContext.Object);
        productRepo = new ProductRepository(mockContext.Object);
        controller = new ProductController(productRepo, unitOfWork, brandRepo, categoryRepo, segmentRepo, typeRepo);
    }

    [Test]
    public void TestReturnEditedModel()
    {
        Product product = productList.ElementAt<Product>(1);
        product.BrandId = 3;
        product.CategoryId = 2;
        product.SegmentId = 2;
        product.TypeId = 3;

        controller.Edit(product, "Return value");

        Product result = productRepo.Get(product.Id);
        Assert.AreEqual(product.Id, result.Id);
        Assert.AreEqual(3, result.BrandId);
        Assert.AreEqual(2, result.CategoryId);
        Assert.AreEqual(2, result.SegmentId);
        Assert.AreEqual(3, result.TypeId);
    }
}

I provided only the test that is failing.我只提供了失败的测试。

Here is the controller function being called这是被调用的控制器函数

[HttpPost]
public ActionResult Edit([Bind(Include = "Id,Title,ParentAsin,ReleaseDate,BrandId,CategoryId,SegmentId,TypeId")]Product model, string returnAction)
{
    if(!ModelState.IsValid)
    {
        Dictionary<string, int> selectedIds = new Dictionary<string, int>();
        selectedIds.Add("BrandId", model.BrandId);
        selectedIds.Add("CategoryId", model.CategoryId);
        selectedIds.Add("SegmentId", model.SegmentId);
        selectedIds.Add("TypeId", model.TypeId);
        PopulateAllDropDownLists(selectedIds);
        return View(model);
    }

    model.Brand = _brandRepo.Get(model.BrandId);
    model.Category = _categoryRepo.Get(model.CategoryId);
    model.Segment = _segmentRepo.Get(model.SegmentId);
    model.Type = _typeRepo.Get(model.TypeId);
    _repository.Update(model);
    _unitOfWork.SaveChanges();
    return RedirectToAction(returnAction);
}

_brandRepo is of type IBrandRepository and after all the implementations and inheritance, the function Get() is in a Generic repository class. _brandRepo的类型是IBrandRepository的实现和继承毕竟,函数Get()是在一个通用存储库类。

Here is the Get function that is being called.这是正在调用的 Get 函数。

public virtual TEntity Get(TId id)
{
    return this.DbSet.Single(x => (object)x.Id == (object)id);
}

The return line is the what is throwing the error.返回行是抛出错误的内容。

Since this is a test and I'm mocking the data, I know that the Id being passed in is correct.由于这是一个测试并且我正在模拟数据,因此我知道传入的 Id 是正确的。 It starts off as an int and the Id of Brand is an int as well, but to make this generic, the property is of type TId is a generic type of the interface TEntity which all models implement.它从一个int开始, BrandId也是一个int ,但为了使这个通用,属性的类型TId是所有模型实现的接口TEntity的通用类型。

Here is TEntity这是 TEntity

public interface IEntity<TId>
{
    /// <summary>
    /// Gets or sets the unique identifier.
    /// </summary>
    /// <value>The unique identifier.</value>
    TId Id { get; set; }
}

I'm not sure if this is a mocking issue or an issue with using generic types.我不确定这是一个嘲笑问题还是使用泛型类型的问题。 Can someone help with this.有人可以帮忙吗。

That looks way too complicated;这看起来太复杂了; you could just use a generic method to create a mock for any DbSet...您可以使用通用方法为任何 DbSet 创建模拟...

public static class DbSetMock
{
    public static Mock<DbSet<T>> CreateFrom<T>(List<T> list) where T : class
    {
        var internalQueryable = list.AsQueryable();
        var mock = new Mock<DbSet<T>>();
        mock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(internalQueryable.Provider);
        mock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(internalQueryable.Expression);
        mock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(internalQueryable.ElementType);
        mock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(()=> internalQueryable.GetEnumerator());
        mock.As<IDbSet<T>>().Setup(x => x.Add(It.IsAny<T>())).Callback<T>(element => list.Add(element));
        mock.As<IDbSet<T>>().Setup(x => x.Remove(It.IsAny<T>())).Callback<T>(element => list.Remove(element));
        return mock;
    }
}

Then you can use it like:然后你可以像这样使用它:

 var mockBrandSet = DbSetMock.CreateFrom(brandList);

Since the data inside of the list and the DbSet are the same you can check the list to assert your manipulations.由于列表中的数据和 DbSet 中的数据相同,您可以检查列表以断言您的操作。

If at any point you try to access the DbSets via their properties and not via Set<> is what would cause that problem if they were not setup.如果在任何时候您尝试通过 DbSet 的属性而不是通过Set<>访问它们,如果未设置它们,则会导致该问题。 Although call base was true in the original example, the DbContext would internally try to discover DbSets and initializes them which would fail when mocking the DbContext.尽管原始示例中的 call base 为 true,但DbContext会在内部尝试发现DbSets并初始化它们,这在模拟 DbContext 时会失败。 This is what they would have to setup in the mock to override the default behavior.这是他们必须在模拟中设置以覆盖默认行为的内容。

var mockContext = new Mock<ApplicationDbContext>();
mockContext.Setup(m => m.Set<Product>()).Returns(mockProductSet.Object);
mockContext.Setup(m => m.Set<Brand>()).Returns(mockBrandSet.Object);
mockContext.Setup(m => m.Products).Returns(mockProductSet.Object);
mockContext.Setup(m => m.Brands).Returns(mockBrandSet.Object);

Also when setting up the GetEnumerator() , use a function to allow for multiple call同样在设置GetEnumerator() ,使用一个函数来允许多次调用

eg例如

mockProductSet.As<IQueryable<Product>>()
    .Setup(m => m.GetEnumerator())
    .Returns(() => productData.GetEnumerator());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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