简体   繁体   English

如何使用 moq 和 xunit 测试业务逻辑方法?

[英]How to test business logic methods using moq and xunit?

I'm using generic repository pattern and this code from my business logic.我正在使用通用存储库模式和我的业务逻辑中的这段代码。

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 Interface: IFolderService 接口:

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

I want to test GetFoldersByUserId(int id) method and i tried this:我想测试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);
        } 
    }

When I start the test, the actual value is null and the test fails.当我开始测试时,实际值为 null 并且测试失败。 GetSampleFolder method has a list of Folders and return this list. GetSampleFolder方法有一个文件夹列表并返回此列表。 My question is how to test GetFoldersByUserId(int id) method?我的问题是如何测试GetFoldersByUserId(int id)方法?

The test below shows how to appropriately setup mocks.下面的测试显示了如何正确设置模拟。

The tricky part is the matching on expression, which is not supported by Moq.棘手的部分是表达式的匹配,Moq 不支持。 That's why I used It.IsAny matcher there.这就是我在那里使用It.IsAny匹配器的原因。

[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);
    }
}

If you really wanted to test against the correctness of the expression which handed over to GetAllByFilter by GetFoldersByUserId you need to do some extra work.如果您真的想测试由GetFoldersByUserId移交给GetAllByFilter的表达式的正确性,您需要做一些额外的工作。 I personally used to match against the expression.ToString() result, according to your test case instead of It.IsAny... :我个人曾经根据您的测试用例而不是It.IsAny...来匹配 expression.ToString() 结果:

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

But to make this to work you should partially evaluate the expression before to explicitly replace encapsulated variable references and constant references with the actual value.但要使其正常工作,您应该在使用实际值显式替换封装的变量引用和常量引用之前对表达式进行部分评估。 Without this step exp.ToString() would look like this:如果没有这一步exp.ToString()将如下所示:

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

where things like StackOverflow.UnitTest1+FolderManager would be the parts leading the actual encapsulated id variable's place in your code. StackOverflow.UnitTest1+FolderManager之类的东西将是导致实际封装的id变量在您的代码中的位置的部分。

Instead of using the exp.ToString() method you can always modify your test to actually use the expression instead of match on it:除了使用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);
    }
}

This way the appropriateness of the constructed expression gets tested against your expectations given the test data.这样,在给定测试数据的情况下,可以根据您的期望来测试构造表达式的适当性。

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

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