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

如何Mock返回类型含只读属性与内部构造函数的第三方接口?

解决Moq模拟EventStore的StreamEventsSlice返回值问题

我太懂这种被第三方库的封闭设计卡壳的感觉了!你遇到的问题是EventStore的StreamEventsSlice因为内部构造函数+只读字段的设计,没法直接用常规方式实例化来给Moq做返回值,下面给你几个实用的解决方案:

方案1:用反射绕过访问限制实例化

既然StreamEventsSlice的构造函数是internal的,我们可以用反射直接调用它的构造函数,传入需要的参数来创建实例(毕竟只读字段都是在构造时赋值的)。

代码示例:

// 先获取StreamEventsSlice的内部构造函数,注意要和实际构造函数的参数列表匹配
var constructor = typeof(StreamEventsSlice)
    .GetConstructor(
        BindingFlags.Instance | BindingFlags.NonPublic,
        null,
        // 这里替换成StreamEventsSlice实际的构造参数类型,比如string, long, int, bool等
        new[] { typeof(string), typeof(long), typeof(int), typeof(bool), typeof(SliceReadStatus), typeof(List<ResolvedEvent>), typeof(long), typeof(long), typeof(bool) },
        null);

// 调用构造函数创建实例,传入你需要的测试数据
var testSlice = constructor.Invoke(new object[] {
    "test-stream",    // Stream字段值
    0,                // Start
    10,               // Count
    true,             // ResolveLinkTos
    SliceReadStatus.Success, // 读取状态
    new List<ResolvedEvent>(), // 事件列表
    0,                // NextEventNumber
    9,                // LastEventNumber
    false             // IsEndOfStream
}) as StreamEventsSlice;

// 配置Moq返回这个实例
var connectionMock = new Mock<IEventStoreConnection>();
connectionMock.Setup(s => s.ReadStreamEventsForwardAsync(
        It.IsAny<string>(), 
        It.IsAny<long>(), 
        It.IsAny<int>(), 
        It.IsAny<bool>(), 
        It.IsAny<UserCredentials>()))
    .ReturnsAsync(testSlice);

小提示:你需要对照EventStore版本里StreamEventsSlice的实际构造参数来调整上面的类型数组和参数值,避免构造失败。

方案2:封装适配层解耦第三方依赖

如果不想用反射这种“hack”手法,更优雅的方式是在业务代码和EventStore ClientAPI之间加一层自己的接口,屏蔽掉第三方库的具体类型。

步骤1:定义自己的接口和DTO

// 自定义的读取接口
public interface IEventStoreStreamReader
{
    Task<MyStreamEventsSlice> ReadForwardAsync(
        string streamId, 
        long startPosition, 
        int eventCount, 
        bool resolveLinks, 
        UserCredentials credentials = null);
}

// 自定义的DTO,完全由你控制
public class MyStreamEventsSlice
{
    public string StreamId { get; set; }
    public bool IsEndOfStream { get; set; }
    public List<MyResolvedEvent> Events { get; set; } = new();
    // 其他你需要的属性
}

// 自定义事件DTO(可选,根据需求调整)
public class MyResolvedEvent
{
    // 业务需要的事件属性
}

步骤2:实现适配器调用真实EventStore

public class EventStoreStreamReaderAdapter : IEventStoreStreamReader
{
    private readonly IEventStoreConnection _eventStoreConnection;

    public EventStoreStreamReaderAdapter(IEventStoreConnection eventStoreConnection)
    {
        _eventStoreConnection = eventStoreConnection;
    }

    public async Task<MyStreamEventsSlice> ReadForwardAsync(
        string streamId, 
        long startPosition, 
        int eventCount, 
        bool resolveLinks, 
        UserCredentials credentials = null)
    {
        var originalSlice = await _eventStoreConnection.ReadStreamEventsForwardAsync(
            streamId, startPosition, eventCount, resolveLinks, credentials);
        
        // 把EventStore的类型映射到自定义DTO
        return new MyStreamEventsSlice
        {
            StreamId = originalSlice.Stream,
            IsEndOfStream = originalSlice.IsEndOfStream,
            Events = originalSlice.Events.Select(e => new MyResolvedEvent
            {
                // 映射具体属性
            }).ToList()
        };
    }
}

步骤3:测试时模拟自定义接口

// 直接模拟自己的接口,完全不用管EventStore的封闭类型
var readerMock = new Mock<IEventStoreStreamReader>();
readerMock.Setup(r => r.ReadForwardAsync(
        It.IsAny<string>(), 
        It.IsAny<long>(), 
        It.IsAny<int>(), 
        It.IsAny<bool>(), 
        It.IsAny<UserCredentials>()))
    .ReturnsAsync(new MyStreamEventsSlice
    {
        StreamId = "test-stream",
        IsEndOfStream = true
    });

// 把这个mock注入到业务类中测试即可

这个方案的好处是彻底解耦了业务代码和第三方库,后续哪怕EventStore API更新,你只需要修改适配器,业务和测试代码都不用动。

方案3:简化测试(仅验证调用场景)

如果你的测试只需要验证ReadStreamEventsForwardAsync是否被正确调用,不需要返回值的具体属性,可以直接返回null或者用回调验证参数:

var connectionMock = new Mock<IEventStoreConnection>();
connectionMock.Setup(s => s.ReadStreamEventsForwardAsync(
        It.IsAny<string>(), 
        It.IsAny<long>(), 
        It.IsAny<int>(), 
        It.IsAny<bool>(), 
        It.IsAny<UserCredentials>()))
    .ReturnsAsync((StreamEventsSlice)null)
    .Callback<string, long, int, bool, UserCredentials>((stream, start, count, resolve, creds) =>
    {
        // 在这里验证参数是否符合预期
        Assert.Equal("expected-stream-id", stream);
        Assert.Equal(0, start);
    });

内容的提问来源于stack exchange,提问作者J Lewis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:26:00