单元测试 - 模拟 - 可覆盖问题 - 扩展方法
Unit Test - Mock - Issue with Overriddable - Extension Method
我正在尝试为以下 Azure 搜索方法编写单元测试:
public async Task Write(ISearchIndexClient indexClient, Search search)
{
await UploadContents(indexClient, search.SearchContents);
}
private static async Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents) => await Task.Run(() => indexClient.Documents.Index(IndexBatch.Upload(searchContents)));
单元测试代码 1:
public async Task Write_Success()
{
var searchIndexClientMock = new Mock<ISearchIndexClient>();
searchIndexClientMock
.Setup(x => x.Documents.Index(It.IsAny<IndexBatch<Document>>(), It.IsAny<SearchRequestOptions>()))
.Returns(It.IsAny<DocumentIndexResult>()).Callback(() => IndexBatch.Upload(It.IsAny<IEnumerable<Document>>()));
var pushFunction = new SearchIndexWriter();
Search search = new Search();
await pushFunction.Write(searchIndexClientMock.Object, search);
//Assert, Verify checks
}
我收到以下错误:
Message: System.NotSupportedException : Unsupported expression: ... => ....Index(It.IsAny>(), It.IsAny())
Extension methods (here: DocumentsOperationsExtensions.Index) may not be used in setup / verification expressions.
单元测试代码 2:
public async Task Write_Success()
{
var searchIndexClientMock = new Mock<ISearchIndexClient>();
searchIndexClientMock
.SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());
var pushFunction = new SearchIndexWriter();
var search = new Search()
{
SearchContents = new List<dynamic>(),
};
await pushFunction.Write(searchIndexClientMock.Object, search);
//Verify, Assert logic
}
我收到以下错误:
Message: System.NullReferenceException : Object reference not set to an instance of an object.
at Microsoft.Azure.Search.DocumentsOperationsExtensions.IndexAsync[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions, CancellationToken cancellationToken)
at Microsoft.Azure.Search.DocumentsOperationsExtensions.Index[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions)
如何测试上传功能?
根据文档,
只有一种方法Index
,它有两个参数。
在UploadContent
方法中,Index方法只传递一个参数。这两个项目是否存在版本差异?
还有...
searchIndexClientMock
.SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());
在设置中,return 应该是一个具体实例,而不是 It.IsAny<IDocumentsOperations>()
。
您基本上是在尝试测试第 3 方依赖项的功能。尽管这些依赖项具有可以模拟的抽象,但它们需要不必要的设置来隔离它们以进行单元测试,这对我来说是一种代码味道。
我建议抽象出第 3 方依赖性
private readonly ISearchService service;
//...assuming service injected
public SearchIndexWriter(ISearchService service) {
this.service = service;
}
public Task Write(ISearchIndexClient indexClient, Search search) {
return service.UploadContents(indexClient, search.SearchContents);
}
尽量避免与静态问题紧密耦合,因为这会使孤立的单元测试变得困难。
服务定义可以类似于
public interface ISearchService {
Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents);
}
使用包装外部依赖项的简单实现
public class SearchService : ISearchService {
private Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents)
=> indexClient.Documents.IndexAsync(IndexBatch.Upload(searchContents));
}
不要再为尝试测试您无法控制的代码而感到压力。而是专注于您可以控制的逻辑。
public async Task Write_Success() {
//Arrange
var serviceMock = new Mock<ISearchService>();
serviceMock
.Setup(_ => _.UploadContents(It.IsAny<ISearchIndexClient>(), It.IsAny<IReadOnlyCollection<It.AnyType>>())
.ReturnsAsync(new object());
var searchIndexClientMock = Mock.Of<ISearchIndexClient>();
var pushFunction = new SearchIndexWriter(serviceMock.Object);
Search search = new Search();
//Act
await pushFunction.Write(searchIndexClientMock.Object, search);
//Assert, Verify checks
//...
}
我正在尝试为以下 Azure 搜索方法编写单元测试:
public async Task Write(ISearchIndexClient indexClient, Search search)
{
await UploadContents(indexClient, search.SearchContents);
}
private static async Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents) => await Task.Run(() => indexClient.Documents.Index(IndexBatch.Upload(searchContents)));
单元测试代码 1:
public async Task Write_Success()
{
var searchIndexClientMock = new Mock<ISearchIndexClient>();
searchIndexClientMock
.Setup(x => x.Documents.Index(It.IsAny<IndexBatch<Document>>(), It.IsAny<SearchRequestOptions>()))
.Returns(It.IsAny<DocumentIndexResult>()).Callback(() => IndexBatch.Upload(It.IsAny<IEnumerable<Document>>()));
var pushFunction = new SearchIndexWriter();
Search search = new Search();
await pushFunction.Write(searchIndexClientMock.Object, search);
//Assert, Verify checks
}
我收到以下错误:
Message: System.NotSupportedException : Unsupported expression: ... => ....Index(It.IsAny>(), It.IsAny()) Extension methods (here: DocumentsOperationsExtensions.Index) may not be used in setup / verification expressions.
单元测试代码 2:
public async Task Write_Success()
{
var searchIndexClientMock = new Mock<ISearchIndexClient>();
searchIndexClientMock
.SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());
var pushFunction = new SearchIndexWriter();
var search = new Search()
{
SearchContents = new List<dynamic>(),
};
await pushFunction.Write(searchIndexClientMock.Object, search);
//Verify, Assert logic
}
我收到以下错误:
Message: System.NullReferenceException : Object reference not set to an instance of an object.
at Microsoft.Azure.Search.DocumentsOperationsExtensions.IndexAsync[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions, CancellationToken cancellationToken)
at Microsoft.Azure.Search.DocumentsOperationsExtensions.Index[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions)
如何测试上传功能?
根据文档,
只有一种方法Index
,它有两个参数。
在UploadContent
方法中,Index方法只传递一个参数。这两个项目是否存在版本差异?
还有...
searchIndexClientMock
.SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());
在设置中,return 应该是一个具体实例,而不是 It.IsAny<IDocumentsOperations>()
。
您基本上是在尝试测试第 3 方依赖项的功能。尽管这些依赖项具有可以模拟的抽象,但它们需要不必要的设置来隔离它们以进行单元测试,这对我来说是一种代码味道。
我建议抽象出第 3 方依赖性
private readonly ISearchService service;
//...assuming service injected
public SearchIndexWriter(ISearchService service) {
this.service = service;
}
public Task Write(ISearchIndexClient indexClient, Search search) {
return service.UploadContents(indexClient, search.SearchContents);
}
尽量避免与静态问题紧密耦合,因为这会使孤立的单元测试变得困难。
服务定义可以类似于
public interface ISearchService {
Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents);
}
使用包装外部依赖项的简单实现
public class SearchService : ISearchService {
private Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents)
=> indexClient.Documents.IndexAsync(IndexBatch.Upload(searchContents));
}
不要再为尝试测试您无法控制的代码而感到压力。而是专注于您可以控制的逻辑。
public async Task Write_Success() {
//Arrange
var serviceMock = new Mock<ISearchService>();
serviceMock
.Setup(_ => _.UploadContents(It.IsAny<ISearchIndexClient>(), It.IsAny<IReadOnlyCollection<It.AnyType>>())
.ReturnsAsync(new object());
var searchIndexClientMock = Mock.Of<ISearchIndexClient>();
var pushFunction = new SearchIndexWriter(serviceMock.Object);
Search search = new Search();
//Act
await pushFunction.Write(searchIndexClientMock.Object, search);
//Assert, Verify checks
//...
}