NSubstitute在模拟的子类上引发事件

时间:2019-02-17 09:33:18

标签: c# testing mocking nunit nsubstitute

我所拥有的是来自外部库的类的包装器(本例中为WebSocketSharp),包装器类对某些事件(例如建立连接时等)做出反应。

要测试此包装器类,我模拟了WebSocket类,并对该类进行了Raise.EventWith处理,我希望Wrapper类可以对其进行处理。

代码是这样的:

public class WebsocketClient {
    public event EventHandler Connected;

    public WebSocket Connection { get;set; }

    public void ConnectAsync() {
        Connection.OnOpen += Connection_OnOpen;
        Connection.ConnectAsync();
    }

    private void Connection_OnOpen(object sender, System.EventArgs e) {
        Connected?.Invoke(this, new EventArgs());
    }
}

我要编写的测试是:

public void ConnectedTest() {
    var objClient = new WebsocketClient();
    var raised = false;
    objClient.Connected += delegate (object sender, EventArgs e) {
        raised = true;
    }; 
    var objWebSocket = Substitute.For<WebSocketSharp.WebSocket>("wss://localhost:443");
    objClient.Connection = objWebSocket;
    objClient.ConnectAsync();
    objClient.Connection.OnOpen += Raise.EventWith(new object(), new EventArgs());

    objWebSocket.Received().OnOpen += Arg.Any<EventHandler>();
    Assert.IsTrue(raised);
}

显然,事情已经简化了,因为还有更多的事情需要检查,这只是为了使想法更清晰。

在测试中,我想验证两件事,当调用ConnectAsync时,将事件处理程序添加到OnOpen事件中,并且当触发OpenOpen时,我将从正在测试的类中获取一个事件。

我知道响应将是这是“不良设计”,但这并不能真正帮助我,您将如何解决呢?,我需要包装WebSocket类,这不是我的,所以不说了。

在这种情况下,我唯一能想到的是扩展WebSocket类而不是创建包装器,但是我确定还有其他测试需要使扩展不是一种选择,并且编写这样的包装器将例如在使用filesystemwatcher或事件处理程序为私有的计时器时仍需要测试偶数被触发时会发生什么的情况。

为了演示,将如何对此进行测试? (什么也没做,只是为了展示想法)

public class FilesDeleted {
    private FileSystemWatcher _objWatcher;
    private List<string> _lstPaths;

    public event EventHandler ItemsDeleted;

    public FilesDeleted(string pPath) {
        _lstPaths = new List<string>();
        _objWatcher = new FileSystemWatcher(pPath);
    }

    public void Start() {
        _objWatcher.Deleted += _objWatcher_Deleted;
        _objWatcher.EnableRaisingEvents = true;
    }

    private void _objWatcher_Deleted(object sender, FileSystemEventArgs e) {
        _lstPaths.Add(e.FullPath); 
        if(_lstPaths.Count > 10) {
            ItemsDeleted?.Invoke(this, new EventArgs());
        }
    }
}

在测试中,您需要验证从filesystemwatcher中删除10个文件后,您是否从该类中获得了“ ItemsDeleted”事件。

认为FileSystemWatcher示例显示了我最努力的事情

1 个答案:

答案 0 :(得分:1)

您关于不良设计的陈述是准确的。这里的问题是,您紧密地结合到第3部分的实现问题上,而这些问题是您无法控制的,这使得隔离测试代码变得很困难。

为所需功能创建抽象

public interface IWebSocket {
    event EventHandler OnOpen;
    void ConnectAsync();

    //... other members
}

已将该抽象明确注入目标类

public class WebsocketClient {
    private readonly IWebSocket connection;

    public WebsocketClient(IWebSocket connection) {
        this.connection = connection;
    }

    public event EventHandler Connected = delegate { };

    public void ConnectAsync() {
        connection.OnOpen += Connection_OnOpen;
        connection.ConnectAsync();
    }

    private void Connection_OnOpen(object sender, System.EventArgs e) {
        Connected.Invoke(this, new EventArgs());
    }
}

请注意,目标类也不再需要公开/泄漏实现问题。

在生产代码中,抽象Web套接字的实现将包装实际的第三方依赖关系。这将在运行时注入到依赖类中。

public class DefaultWebSocketWrapper : IWebSocket {
    private WebSocket webSocket;

    public DefaultWebSocketWrapper() {
        webSocket = new WebSocket("wss://localhost:443");
    }

    public event EventHandler OnOpen {
        add {
            webSocket.OnOpen += value;
        }
        remove {
            webSocket.OnOpen -= value;
        }
    }

    public void ConnectAsync() {
        webSocket.ConnectAsync();
    }

    //... other members
}

此类无需进行测试,因为它只是您无法控制的外部代码的包装。因此进行测试将浪费时间。

最终结果是,现在您的代码已与外部依赖项脱钩,可以在不影响效果的情况下进行独立测试了,

[TestClass]
public class WebSocketTests {
    [Test]
    public void ConnectedTest() {
        //Arrange
        var webSocketMock = Substitute.For<IWebSocket>();

        var subject = new WebsocketClient(webSocketMock);
        bool raised = false;
        subject.Connected += delegate(object sender, EventArgs e) {
            raised = true;
        };
        subject.ConnectAsync();

        //Act
        webSocketMock.OnOpen += Raise.Event();

        //Assert
        Assert.IsTrue(raised);
    }
}

上面的代码可以安全地测试WebsocketClient,而无需担心外部第三方依赖关系,因为您可以控制所有正在使用的代码。

FileSystemWatcher可以采用与您的问题所述相同的方法。