这个 Moq 单元测试应该(假)检索列表有啥问题?

Posted

技术标签:

【中文标题】这个 Moq 单元测试应该(假)检索列表有啥问题?【英文标题】:What is wrong with this Moq unit test that is supposed to (fake) retrieve a list?这个 Moq 单元测试应该(假)检索列表有什么问题? 【发布时间】:2022-01-19 10:08:29 【问题描述】:

我正在尝试测试这种方法,该方法旨在从数据库中检索数据并将它们存储到 List 中。我制作了这个假列表,旨在通过该方法找到。但是,该方法取而代之的是检索实际数据库并返回实际数量。我错过了什么?

    public class GetAllPhonesTests
    
        private readonly Mock<IRepository<Phone>> _mockPhoneRepository;
        private readonly IPhoneService _phoneService;

        private readonly List<Phone> _fakePhones = new()
        
            new Phone()  Id = 1, Brand = "Herp", Type = "Police" ,
            new Phone()  Id = 2, Brand = "Derp", Type = "Fireman" ,
            new Phone()  Id = 3, Brand = "Zerp", Type = "Nurse" ,
            new Phone()  Id = 4, Brand = "Flurp", Type = "Doctor" ,
            new Phone()  Id = 5, Brand = "Terp", Type = "Teacher" 
        ;

        public GetAllPhonesTests()
        
            _mockPhoneRepository = new Mock<IRepository<Phone>>();
            _phoneService = new PhoneService(_mockPhoneRepository.Object);
        

        [Fact]
        public void Should_ReturnFullList_When_Called()
        
            //Arrange
            _mockPhoneRepository.Setup(x => x.GetRecords(It.IsAny<SqlCommand>())).Returns(_fakePhones);

            //Act
            List<Phone> actual = _phoneService.GetAllPhones();

            //Assert
            actual.Count.Should().Be(_fakePhones.Count); 
        
    

来自服务的其他内容:


        public List<Phone> GetAllPhones()
        
            string query = "SELECT * FROM phones INNER JOIN Brands ON Brands.BrandID=phones.BrandId;";
            using (var command = new SqlCommand(query))
            
                return (List<Phone>)GetRecords(command);
            
        

来自 repo 的其他内容:


        public IEnumerable<T> GetRecords(SqlCommand command)
        
            SqlDataReader reader = null;
            List<T> list = new();

            try
            
                command.Connection = _connection;
                _connection.Open();
                reader = command.ExecuteReader();
                while (reader.Read())
                
                    list.Add(PopulateRecord(reader));
                

                reader.NextResult();
                if (reader.HasRows)
                
                    while (reader.Read())
                    
                        GetDataCount(Convert.ToInt32(reader["Count"].ToString()));
                    
                
                Status(false, "");
            
            catch (Exception ex)
            
                Status(true, ex.Message);
            
            finally
            
                reader.Close();
                _connection.Close();
            

            return list;
        

【问题讨论】:

当您调试时,当您进入 GetAllPhones 时,在从 repo 调用 GetRecords 时是否显示使用模拟 repo? 你能展示你的 GetAllPhones() 方法的实现吗? 不,调用进入实际的仓库。 是存储库上的 GetRecords 和服务上的 GetAllPhones?因为从粘贴的代码看来,GetAllPhones 正在调用自身的方法。那么,GetAllPhones 在存储库中吗? 您需要给我们一个minimal reproducible example。在PhoneService 的构造函数中以某种方式使用了Repository,但您显示的代码实际上并未显示正在使用存储库。相反,PhoneService 似乎有自己的方法GetRecords。我没有看到对 _repository 依赖字段的引用.... 【参考方案1】:

我认为问题可能在于您的设置顺序,因为它应该在您将存储库对象添加到模拟服务之前完成。您可以尝试将其更改为:

public class GetAllPhonesTests

    private readonly Mock<IRepository<Phone>> _mockPhoneRepository;
    private readonly IPhoneService _phoneService;

    private readonly List<Phone> _fakePhones = new()
    
        new Phone()  Id = 1, Brand = "Herp", Type = "Police" ,
        new Phone()  Id = 2, Brand = "Derp", Type = "Fireman" ,
        new Phone()  Id = 3, Brand = "Zerp", Type = "Nurse" ,
        new Phone()  Id = 4, Brand = "Flurp", Type = "Doctor" ,
        new Phone()  Id = 5, Brand = "Terp", Type = "Teacher" 
    ;

    public GetAllPhonesTests()
    
        _mockPhoneRepository = new Mock<IRepository<Phone>>();
        _mockPhoneRepository.Setup(x => x.GetRecords(It.IsAny<SqlCommand> 
         ())).Returns(_fakePhones);
        _phoneService = new PhoneService(_mockPhoneRepository.Object);
    

    [Fact]
    public void Should_ReturnFullList_When_Called()
    
        //Act
        List<Phone> actual = _phoneService.GetAllPhones();

        //Assert
        actual.Count.Should().Be(_fakePhones.Count); 
    

您的服务也应该使用 DI 来获取使用的存储库实现,例如:

    private readonly IRepository<Phone> _phoneRepo;

    public PhoneService(IRepository<Phone> phoneRepo)
        _phoneRepo = phoneRepo;
    

    public List<Phone> GetAllPhones()
    
        string query = "SELECT * FROM phones INNER JOIN Brands ON Brands.BrandID=phones.BrandId;";
        using (var command = new SqlCommand(query))
        
            return (List<Phone>)_phoneRepo.GetRecords(command);
        
    

【讨论】:

那更糟。看他的代码:phoneservice不使用repository,而是有一个本地的GetRecords... 不幸的是,就像我使用 sql 一样,我也(还)不允许使用 DI。感谢收看。 那么这就是问题所在,即使您正在模拟存储库,您也没有在服务中使用该实现,即使没有 DI,您仍然可以拥有如上所示的存储库并手动传递它,尝试以我上面显示的方式实现服务,看看你的测试是否有效,如果没有 DI,你必须做的是每次使用服务时提供一个存储库实例,例如: new PhoneService(new Repository() ) 只需在调用方法之前完成设置。在 mock 用作构造函数参数之前或之后完成都没有关系。

以上是关于这个 Moq 单元测试应该(假)检索列表有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章

单元测试在C#,Moq中调用SAP异步Web服务

单元测试之Mock(Moq)

C#单元测试--如何使用moq.mock进行依赖注入

使用 moq 对实体框架进行单元测试

如何使用MOQ进行单元测试

使用 Moq 模拟单元测试的异步方法