如何使用 moq 和 xunit 测试业务逻辑方法?
How to test business logic methods using moq and 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
方法有一个文件夹列表和 return 这个列表。我的问题是如何测试 GetFoldersByUserId(int id)
方法?
下面的测试展示了如何正确设置模拟。
棘手的部分是表达式匹配,Moq不支持。
这就是我在那里使用 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);
}
}
如果你真的想测试 GetFoldersByUserId
交给 GetAllByFilter
的表达式的正确性,你需要做一些额外的工作。
根据您的测试用例,我个人习惯于匹配 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(Whosebug.UnitTest1+FolderManager+<>c__DisplayClass3_0).id) AndAlso (I.IsDeleted == False)) AndAlso (I.ParentFolderId == null))
其中 Whosebug.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);
}
}
通过这种方式,可以根据您在给定测试数据时的预期来测试构造表达式的适当性。
我正在使用通用存储库模式并且 此代码来自我的业务逻辑。
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
方法有一个文件夹列表和 return 这个列表。我的问题是如何测试 GetFoldersByUserId(int id)
方法?
下面的测试展示了如何正确设置模拟。
棘手的部分是表达式匹配,Moq不支持。
这就是我在那里使用 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);
}
}
如果你真的想测试 GetFoldersByUserId
交给 GetAllByFilter
的表达式的正确性,你需要做一些额外的工作。
根据您的测试用例,我个人习惯于匹配 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(Whosebug.UnitTest1+FolderManager+<>c__DisplayClass3_0).id) AndAlso (I.IsDeleted == False)) AndAlso (I.ParentFolderId == null))
其中 Whosebug.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);
}
}
通过这种方式,可以根据您在给定测试数据时的预期来测试构造表达式的适当性。