Reputation: 569
What I have is a wrapper around a class from an external library (this case it's WebSocketSharp), the wrapper class reacts on certain events like when a connection was established etc.
To test this wrapper class I've mocked the WebSocket class and did Raise.EventWith on that class, and I'm expecting the Wrapper class to do w/e handling it should.
Code is something like this:
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());
}
}
The test that I wanted to write is:
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);
}
Obviously things were simplified a bit as there are a few more things to check, this is just to get the idea across.
Here in the test I want to verify 2 things, that when ConnectAsync is called an eventhandler is added to the OnOpen event and when the OnOpen is triggered I'm getting an event back from the class I'm testing.
What I know the response will be is that this is 'bad design', but that doesn't really help me much, how would you solve this?, I need to wrap the WebSocket class, this is not mine so have no say over it.
The only thing in this scenario I can think of is extending WebSocket class instead of making a wrapper, but I'm sure there are other tests I need to make where extending isn't an option and writing a wrapper like this will be needed, example when using a filesystemwatcher or a timer where the event handler is private and still want to test what happens when the even is fired.
To demonstrate, how would this for example be tested? (didn't run anything, it's just to show the idea)
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());
}
}
}
In the test you'd want to verify that after 10 file deleted events from the filesystemwatcher you get the "ItemsDeleted" event from this class.
Think the FileSystemWatcher example shows with what I'm struggling the most
Upvotes: 0
Views: 1107
Reputation: 247531
Your statement about bad design is accurate. The problem here is that you are tightly coupling to 3rd part implementation concerns you have no control over, that make testing your code in isolation difficult.
Create an abstraction for the desired functionality
public interface IWebSocket {
event EventHandler OnOpen;
void ConnectAsync();
//... other members
}
have that abstraction explicitly injected into the target class
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());
}
}
Note the target class no longer needs to expose/leak the implementation concerns as well.
In production code the implementation of the abstracted web socket will wrap the actual 3rd party dependency. This is what will be injected into dependent classes at run time.
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
}
This class does not need to be tested as it is simply a wrapper for external code you have no control over. Thus testing it would be a waste of time.
The eventual result is that now your code is decoupled from external dependencies and can be tested in isolation without knock on effects,
[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);
}
}
The above safely tests the WebsocketClient
without any worries about external 3rd party dependencies as you are in control of all the code being used.
The same approach can be taken with the FileSystemWatcher
as described in your question.
Upvotes: 1