【问题标题】:How to test business logic methods using moq and xunit?如何使用 moq 和 xunit 测试业务逻辑方法?
【发布时间】:2021-06-16 14:39:29
【问题描述】:

我正在使用通用存储库模式和 这段代码来自我的业务逻辑。

public class FolderManager : GenericManager<Folder>, IFolderService
{
    private readonly IGenericDal<Folder> _genericDal;
    private readonly IFolderDal _folderDal;
    public FolderManager(IFolderDal folderDal,IGenericDal<Folder> genericDal) : base(genericDal)
    {
        _genericDal = genericDal;
        _folderDal = folderDal;
    }

    public async Task<List<Folder>> GetFoldersByUserId(int id)
    {
        return await _genericDal.GetAllByFilter(I => I.AppUserId == id && I.IsDeleted == false && I.ParentFolderId==null);
    } ...another methods

IFolderService 接口:

public interface IFolderService : IGenericService<Folder>
{
    Task<List<Folder>> GetFoldersByUserId(int id);
}     ...another methods

我想测试GetFoldersByUserId(int id) 方法,我试过这个:

public class FolderServiceTest
{  
    private readonly FolderManager _sut;
    private readonly Mock<IGenericDal<Folder>> _folderRepoMock = new Mock<IGenericDal<Folder>>();
    private readonly Mock<IFolderDal> _folderDalMock = new Mock<IFolderDal>();
    public FolderServiceTest()
    {
        _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);
    }

    [Fact]
    public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
    {
        //Arrange
        Mock<IFolderService> folderServiceMock = new Mock<IFolderService>();
        folderServiceMock.Setup(x => x.GetFoldersByUserId(It.IsAny<int>())).ReturnsAsync(GetSampleFolder);
        
        var expected = GetSampleFolder();

        //Act
         
        //returns null beacuse _sut does not work with the setup I wrote above
        //how can i test this method ? 
        var actual = await _sut.GetFoldersByUserId(1); /* */

        //Assert 
        Assert.Equal(expected.Count, actual.Count);

        for (int i = 0; i < expected.Count; i++)
        {
            Assert.Equal(expected[i].FolderName, actual[i].FolderName);
            Assert.Equal(expected[i].Size, actual[i].Size);
        } 
    }

当我开始测试时,实际值为 null,测试失败。 GetSampleFolder 方法有一个文件夹列表并返回此列表。我的问题是如何测试GetFoldersByUserId(int id) 方法?

【问题讨论】:

  • 您的代码看起来不一致: 1. 为什么要定义folderServiceMock? 2.你在说什么IUserService? 3、“不接受构造函数”是什么意思? GetFoldersByUserId 在您的测试设置中调用 _genericDal,即 _folderRepoMock。它返回 null 是因为您没有设置 _folderRepoMock.GetAllByFilter 方法来返回任何内容,因此模拟返回 null 而您的 GetFoldersByUserId 返回此 null。
  • 谢谢你,我想我明白了。我写了 folderServiceMock 因为我认为我可以在 _sut 对象中使用它。我在上面的代码中编辑了注释行。我设置了_folderRepoMock.Setup(x =&gt; x.GetAllByFilter(I =&gt; I.AppUserId == It.IsAny&lt;int&gt;() &amp;&amp; I.IsDeleted == false &amp;&amp; I.ParentFolderId == null)).ReturnsAsync(GetSampleFolder),我得到了同样的错误。你有什么想法吗?
  • 你错过了添加最重要的东西:错误本身:) NotSupportedException: Unsupported expression: I =&gt; (((I.AppUserId == IsAny()) AndAlso (I.IsDeleted == False)) AndAlso (I.ParentFolderId == null)) 对吗?
  • 对不起,我忘了添加。我收到此错误System.NullReferenceException : Object reference not set to an instance of an object.,它来自Assert.Equal(expected.Count, actual.Count); 行。我调试了测试代码,我看到 actual 返回 null
  • 当您跳过_folderRepoMock 设置时,您会得到 nullrefex。但是当你进行必要的_folderRepoMock 设置时,你会得到我提到的notsupex。那是因为 Moq 不支持匹配 Lamba 表达式。

标签: c# .net .net-core moq xunit


【解决方案1】:

下面的测试展示了如何正确设置模拟。

棘手的部分是表达式的匹配,Moq 不支持。 这就是我在那里使用It.IsAny matcher 的原因。

[Fact]
public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
{
    //Arrange
    var expected = GetSampleFolder();

    var _folderRepoMock = new Mock<IGenericDal<Folder>>();
    _folderRepoMock
        .Setup(x => x.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>()))
        .ReturnsAsync(expected);

    var _folderDalMock = new Mock<IFolderDal>();

    var _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);


    //Act
    var actual = await _sut.GetFoldersByUserId(1);

    //Assert 
    Assert.Equal(expected.Count, actual.Count);

    for (int i = 0; i < expected.Count; i++)
    {
        Assert.Equal(expected[i].FolderName, actual[i].FolderName);
        Assert.Equal(expected[i].Size, actual[i].Size);
    }
}

如果您真的想测试由GetFoldersByUserId 移交给GetAllByFilter 的表达式的正确性,您需要做一些额外的工作。 我个人曾经根据你的测试用例而不是It.IsAny...来匹配 expression.ToString() 结果:

Is.Is<Expression<Func<Folder, bool>>>(exp => exp.ToString() == "I => I.AppUserId == 1 && I.IsDeleted == false && I.ParentFolderId==null")

但要使其正常工作,您应该先对表达式进行部分评估,然后用实际值显式替换封装的变量引用和常量引用。如果没有这一步,exp.ToString() 将如下所示:

I => (((I.AppUserId == value(StackOverflow.UnitTest1+FolderManager+<>c__DisplayClass3_0).id) AndAlso (I.IsDeleted == False)) AndAlso (I.ParentFolderId == null))

StackOverflow.UnitTest1+FolderManager 之类的内容将是导致实际封装的 id 变量在您的代码中的位置的部分。

除了使用exp.ToString() 方法之外,您始终可以修改您的测试以实际使用表达式而不是对其进行匹配:

// just populate to cover all special cases
private List<Folder> testFolderList = new List<Folder>()
{
    new Folder() { AppUserId=1, IsDeleted=false, ParentFolderId=null, FolderName = "a", Size = 1 },
    new Folder() { AppUserId=1, IsDeleted=true, ParentFolderId=null, FolderName = "b", Size = 2 },
    new Folder() { AppUserId=1, IsDeleted=false, ParentFolderId=2, FolderName = "c", Size = 3 },
    new Folder() { AppUserId=2, IsDeleted=false, ParentFolderId=null, FolderName = "a", Size = 4 },
};


[Fact]
public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist()
{
    //Arrange
    var userId = 1;
    var expected = testFolderList
        // replace with expression based on the _contract_ you expect from GetFoldersByUserId
        .Where(I => I.AppUserId == userId && I.IsDeleted == false && I.ParentFolderId == null) 
        .ToList();

    var _folderRepoMock = new Mock<IGenericDal<Folder>>();
    _folderRepoMock
        .Setup(x => x.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>()))
        .ReturnsAsync((Expression<Func<Folder, bool>> exp) =>
        {
            return testFolderList
                // here we explicitly use the expression we got as parameter
                .Where(exp.Compile())
                .ToList();
        });

    var _folderDalMock = new Mock<IFolderDal>();

    var _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object);


    //Act
    var actual = await _sut.GetFoldersByUserId(userId);

    //Assert 
    Assert.Equal(expected.Count, actual.Count);

    for (int i = 0; i < expected.Count; i++)
    {
        Assert.Equal(expected[i].FolderName, actual[i].FolderName);
        Assert.Equal(expected[i].Size, actual[i].Size);
    }
}

这样,在给定测试数据的情况下,可以根据您的期望来测试构造表达式的适当性。

【讨论】:

  • 非常感谢您的解决方案。我会在其他测试中尝试。
猜你喜欢
  • 1970-01-01
  • 2018-01-26
  • 1970-01-01
  • 2022-01-21
  • 2016-01-04
  • 2012-12-27
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
相关资源
最近更新 更多