You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何Mock Func<Task<泛型类型>>并模拟仓储而非仅模拟代理?

问题描述

我实现了一个泛型类GenericProxy : IProxy,包含如下方法:

public async Task<T> SomeMethod<T>(Func<Task<T>> action, bool ignore) {
        if (ignore) {
            return await action();
        }

        return await proxy<T>.ExecuteAsync(_ => action());
}

编写了如下调用代码:

public async Task<string> StoreBook(string bookName) {
    var findBookResult = await proxy.SomeMethod<string>(async () => await BorrowBookProcess(bookName), false);
    return findBookResult;
}

public async Task<string> BorrowBookProcess (string bookName) {
    // do something
    return await bookRepository.findbook(bookName);
}

单元测试中,以下测试方法可正常运行:

[Fact]
public async void SomeMethod_return_AsExpected() {
    Mock<IProxy> mockPoxy = new Mock<IProxy>();
    string assertResult = "Finded";

    mockPoxy.Setup(
                x => x.SomeMethod(
                    It.IsAny<Func<Task<string>>>(),
                    false))
            .ReturnsAsync(assertResult)
            .Verifiable();

    var getReturn = sut.StoreBook(assertResult);

    assert.equal(getReturn, assertResult);
}

但出于某些原因,我想模拟bookRepository并返回值,而非仅模拟IProxy,请问该如何实现?

解决方案

要直接模拟bookRepository而非IProxy,核心是让被测试对象依赖真实的GenericProxy实例,同时注入Mock的bookRepository,具体步骤如下:

  • 第一步:确保bookRepository是可注入的依赖
    包含StoreBook和BorrowBookProcess的类,需要通过构造函数注入bookRepository对应的抽象接口(如果还没抽象接口,先给bookRepository定义一个IBookRepository接口),示例代码:

    public class BookService {
        private readonly IProxy _proxy;
        private readonly IBookRepository _bookRepository;
    
        // 构造函数注入依赖
        public BookService(IProxy proxy, IBookRepository bookRepository) {
            _proxy = proxy;
            _bookRepository = bookRepository;
        }
    
        public async Task<string> StoreBook(string bookName) {
            var findBookResult = await _proxy.SomeMethod<string>(async () => await BorrowBookProcess(bookName), false);
            return findBookResult;
        }
    
        public async Task<string> BorrowBookProcess(string bookName) {
            // do something
            return await _bookRepository.findbook(bookName);
        }
    }
    
  • 第二步:编写测试代码,MockIBookRepository并使用真实GenericProxy
    测试时不再MockIProxy,而是实例化真实的GenericProxy,同时MockIBookRepository并预设返回值:

    [Fact]
    public async Task StoreBook_ShouldReturnBookFromRepository() {
        // 测试数据准备
        string testBookName = "Test Book";
        string expectedResult = "Finded Test Book";
    
        // Mock 书籍仓储
        var mockBookRepo = new Mock<IBookRepository>();
        mockBookRepo.Setup(repo => repo.findbook(testBookName))
                    .ReturnsAsync(expectedResult);
    
        // 实例化真实代理和被测试对象
        var realProxy = new GenericProxy(); // 若GenericProxy有依赖,需同步注入对应Mock
        var sut = new BookService(realProxy, mockBookRepo.Object);
    
        // 执行测试方法
        var actualResult = await sut.StoreBook(testBookName);
    
        // 断言结果匹配
        Assert.Equal(expectedResult, actualResult);
    
        // 验证仓储方法被正确调用
        mockBookRepo.Verify(repo => repo.findbook(testBookName), Times.Once);
    }
    
  • 注意事项

    • 原测试用async void是不良实践,建议改为async Task,避免异步测试的潜在问题;
    • 如果GenericProxy本身有其他依赖,也要为这些依赖注入Mock,保证测试环境的隔离性;
    • 需确保GenericProxy的SomeMethod在ignore=false时,能正确执行传入的action逻辑,这样Mock的bookRepository才会被调用到。

内容的提问来源于stack exchange,提问作者Carrie Kaski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 05:53:19