c# - 如何使用 moq 和 xunit 测试业务逻辑方法?
问题描述
我正在使用通用存储库模式和我的业务逻辑中的这段代码。
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)
方法?
解决方案
下面的测试显示了如何正确设置模拟。
棘手的部分是表达式的匹配,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);
}
}
如果你真的想测试你传递给你的表达式的正确性,GetAllByFilter
你GetFoldersByUserId
需要做一些额外的工作。我个人曾经根据您的测试用例而不是匹配 expression.ToString() 结果It.IsAny...
:
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);
}
}
这样,在给定测试数据的情况下,可以根据您的期望来测试构造表达式的适当性。
推荐阅读
- javascript - 窗口宽度中的Jquery多个条件类似于媒体查询
- android - 如何检查 UI 是否正在刷新?
- javascript - 如何将 SCSS 编译为 CSS 并将 CSS 代码作为字符串加载到变量中?
- yaml - Azure Pipelines 中的多行字符串
- reactjs - React confetti:超级表达式必须为空或函数
- typescript - 如何创建使用 typesafe-actions AsyncCreator 的通用异步请求史诗来简化在 Redux 中处理 api 请求流
- laravel - 下拉菜单不适用于 Bootstrap 4 和 Laravel 6
- python - 箱线图和散点图:消失的 X 刻度
- sql - 如果满足任何一个条件,我想提取数据。目前,它似乎只有在满足两个条件时才会拉数据
- python - 在 Matplotlib 中创建圆形变量的原始数据图(无极坐标)