Moq 具有用于单元测试的数据库上下文的服务或服务接口

Moq a service or service interface with a db context for unit testing

所以我正在尝试测试我在 .net core 5.0 中编码的 webapp,并且我有一个 classic MVC 模型和服务 classes。现在我正在尝试使用最小起订量对解决方案进行单元测试以模拟我的数据库和我的测试 运行,但我刚刚注意到在调试时它们都是错误的。似乎它实际上并没有连接到模拟服务或数据库本身......我已经开始模拟服务接口,但它似乎仍然无法正常工作。将不胜感激。

服务class:

public class InventoryService : IInventoryService
{
    private readonly DBContext _db;

    public InventoryService(DBContext db)
    {
        _db = db;
    }

    public List<Inventory> GetInventories(string id)
    {
        var inventories = (from i in _db.Inventories where i.userId.Equals(id) select i).ToList();
        return inventories;
    }

    public void CreateInventory(Inventory newInventory)
    {
        _db.Inventories.Add(newInventory);
        _db.SaveChanges();
    }

    public bool DeleteInventory(Guid Id, string UserId)
    {
        var inventory = _db.Inventories.Find(Id);

        if (inventory == null)
            return false;

        if (inventory.userId != UserId)
            return false;

        //Delete items using item service
        var items = from i in _db.Items where i.inventoryId.Equals(Id) select i;
        foreach(var i in items)
        {
            _db.Items.Remove(i);
        }

        _db.Inventories.Remove(inventory);
        _db.SaveChanges();
        return true;
    }

}

}

它使用的服务接口:

public interface IInventoryService
{
    public List<Inventory> GetInventories(string id);
    public void CreateInventory(Inventory newInventory);
    public bool DeleteInventory(Guid Id, string UserId);
}

}

型号:

public class Inventory
{
    // The Id field is a unique identifier for a specific inventory
    [Key]
    public Guid Id { get; set; }

    // The name of the Inventory. Required to have a value
    [Required]
    [DisplayName("Inventory Name")]
    public string name { get; set; }

    public string userId { get; set; }
}

}

单元测试:

public class InventoryTests
{

    // Unit test defined for the get user inventories, a valid ID will always be passed in so no need for negative testing
    [Fact]
    public void Get_user_inventories_with_valid_id()
    {
        //ARRANGE
        Guid theId1 = new("00000000-0000-0000-0000-000000000001");
        Guid theId2 = new("00000000-0000-0000-0000-000000000002");

        string u1 = "xxx";
        string u2 = "yyy";

        var mockIn = new Mock<IInventoryService>();

        var data = new List<Inventory>
        {
            new Inventory { Id = theId1, name = "Mums 1", userId = u1},
            new Inventory { Id = theId1, name = "Mums 1.2", userId = u1},

        }.AsQueryable();

        var mockSet = new Mock<DbSet<Inventory>>();
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        Mock<IInventoryService> myser = new Mock<IInventoryService>();

        //ACT

        myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());

        var tinvs = myser.Object.GetInventories(u1);


        //ASSERT
        Assert.Equal(2 , tinvs.Count);

    }


    //Unit test for creating an inventory, a valid new inventory object will always be passed in so need for negative testing
    [Fact]
    public void Creating_An_Inventory()
    {
        //ARRANGE
        Guid theId2 = new("00000000-0000-0000-0000-000000000001");

        string u1 = "xxx";
        string u2 = "yyy";

        var mockIn = new Mock<IInventoryService>();

        var data = new List<Inventory>
        {

        }.AsQueryable();

        var mockSet = new Mock<DbSet<Inventory>>();
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        //ACT

        Mock<IInventoryService> myser = new Mock<IInventoryService>();
        myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());

        Inventory thenew = new Inventory { Id = theId2, name = "Mums 2", userId = u2 };
        myser.Object.CreateInventory(thenew);


        //ASSERT
        Assert.NotNull(data);
    }


    //Unit test for deleting an inventory. It will always be valid because a guid and user id will be passed in automatically 
    [Fact]
    public void Deleting_An_Inventory()
    {
        //ARRANGE
        Guid theId1 = new("00000000-0000-0000-0000-000000000001");
        Guid theId2 = new("00000000-0000-0000-0000-000000000002");


        string u1 = "123";


        //var mockIn = new Mock<IInventoryService>();

        var data = new List<Inventory>
        {
            new Inventory { Id = theId1, name = "testinv 1", userId = u1},

        }.AsQueryable();

        var mockSet = new Mock<DbSet<Inventory>>();
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        //ACT

        Mock<IInventoryService> myser = new Mock<IInventoryService>();

        myser.Setup(x => x.GetInventories(u1)).Returns(mockSet.Object.ToList());


        myser.Object.DeleteInventory(theId1, u1);

        var updInvs = myser.Object.GetInventories(u1);
        int x = updInvs.Count;

        //ASSERT
        Assert.Equal(0, x);
    }

}

}

我相当确定这是我设置单元测试模拟时的一个小错误。任何帮助都会很棒。

所以,如果你想测试你的 InventoryService,你必须像你那样模拟你的 dbContext。最好像微软建议的那样使用 EF InMemoryDatabase https://docs.microsoft.com/en-us/ef/core/testing/#unit-testing

关于你的测试,我做了一些修改,以测试InventoryService。它在调试模式下通过。

public class InventoryService : IInventoryService
    {
        private readonly DBContext _db;

        public InventoryService(DBContext db)
        {
            _db = db;
        }

        public List<Inventory> GetInventories(string id)
        {
            //i change inventories to _db.Set<Inventory> which able to be mock
            var inventories = (from i in _db.Set<Inventory>() where i.userId.Equals(id) select i).ToList();
            return inventories;
        }
        //ANOTHER CODE
}
        [Fact]
        public void Get_user_inventories_with_valid_id()
        {
            //ARRANGE
            Guid theId1 = new("00000000-0000-0000-0000-000000000001");
            Guid theId2 = new("00000000-0000-0000-0000-000000000002");

            string u1 = "xxx";
            string u2 = "yyy";

            var mockIn = new Mock<IInventoryService>();

            var data = new List<Inventory>
            {
                new Inventory { Id = theId1, name = "Mums 1", userId = u1 },
                new Inventory { Id = theId1, name = "Mums 1.2", userId = u1 },
            }.AsQueryable();

            var mockSet = new Mock<DbSet<Inventory>>();
            mockSet.As<IQueryable<Inventory>>().Setup(m => m.Provider).Returns(data.Provider);
            mockSet.As<IQueryable<Inventory>>().Setup(m => m.Expression).Returns(data.Expression);
            mockSet.As<IQueryable<Inventory>>().Setup(m => m.ElementType).Returns(data.ElementType);
            mockSet.As<IQueryable<Inventory>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());


            //HERE
            var dbMock = new Mock<DBContext>();
            dbMock.Setup(x => x.Set<Inventory>()).Returns(mockSet.Object);
            var myser = new InventoryService(dbMock.Object);

            //ACT
            //AND HERE
            var tinvs = myser.GetInventories(u1);


            //ASSERT
            Assert.Equal(2, tinvs.Count);
        }

如果您想制作 InMemory 数据库或在真实 SQL 数据库上进行测试,请查看这些问题。