The code to be tested is as follows:
//pseudo code
public class ConnectionManager: IConnectionManager
(
ILogService _logService;
IConnector _iConnector;
public ConnectionManager(IConnector connector, ILogService logService)
{
this._logService = logService;
this._iConnector = connector;
}
public Task<string> ConnectToIP(string ip)
{
try
{
return Task.Run(() => _iConnector.Connect(ip));
}
catch (Exception ex)
{
_logService.Error(ex);
throw;
}
}
)
For testing this I have write following code:
//pseudo code
[Fact]
public async Task ConnectToIP_Exception_LogsAndThrows()
{
// Arrange
Mock<ILogService> _mockLogService = new Mock<ILogService>();
Mock<IConnector> _mockConnector = new Mock<IConnector>();
IConnectionManager _manager = new ConnectionManager(_mockConnector.Object, _mockLogService.Object);
var ip = "192.168.9.1";
var exception = new InvalidOperationException("Test Exception");
_mockConnector.Setup(r => r.Connect(It.IsAny<string>())).ThrowsAsync(exception);
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => _manager.ConnectToIP(ip));
_mockLogService.Verify(l => l.Error(exception), Times.Once);
}
The code is enclosed in Task.Run, The issue is my tests never reach the catch block, the catch block is not executed,
I want to cover the catch too
This is not a problem of your test. It is actually a bug in the code under test.
The original code:
public Task<string> ConnectToIP(string ip)
{
try
{
return Task.Run(() => _iConnector.Connect(ip)); // <= returns before exception is thrown.
}
catch (Exception ex)
{
_logService.Error(ex);
throw;
}
}
returns a Task<string> before the exception is actually thrown. So by that time, control flow has moved outside the try/catch and will either be lost to nirvana or blow up at the point of unwrapping.
Depending on what your (team's) goal is in creating this "fake" async API, you could await the Task:
public async Task<string> ConnectToIP(string ip)
{
try
{
return await Task.Run(() => _iConnector.Connect(ip));
}
catch (Exception ex)
{
_logService.Error(ex);
throw;
}
}
which will unwrap here and lead to the exception being caught at the expected place.
Or you can actually execute synchronously (which works, but I do not recommend because it blocks the executing thread but "pretends" to work non-blocking):
public Task<string> ConnectToIP(string ip)
{
try
{
var connectionResult = _iConnector.Connect(ip);
return Task.FromResult(connectionResult);
}
catch (Exception ex)
{
_logService.Error(ex);
throw;
}
}
If you are not in the position to make changes to the original code, then you need to raise the issue with your peers and/or higher ups.
In fact, this may be indicative of a lack of knowledge or experience, that should be met with some training. If this happened here, I'd expect to find the same pattern in more places of the codebase.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With