Reputation: 101
Recently, we updated all our project to .NET 8 and now we need to remake the unit test for our SignalR since are now using Microsoft.Azure.Functions.Worker.SignalRService.ServerlessHub
Thing is this is how our function is setup
[SignalRConnection("ConnectionStrings:SignalR")]
public class SignalRMessageProcessor : ServerlessHub
{
private readonly IAuthenticationManager _authenticationManager;
private readonly ILogger<SignalRMessageProcessor> _logger;
public SignalRMessageProcessor(
IServiceProvider serviceProvider,
IAuthenticationManager authenticationManager,
ILogger<SignalRMessageProcessor> logger) : base(serviceProvider)
{
_authenticationManager = authenticationManager;
_logger = logger;
}
[Function("negotiate")]
public async Task<IActionResult> Negotiate(
[HttpTrigger("get", Route = "negotiate/{userId}")] HttpRequest req,
string userId)
{
var statusCode = Authenticate(req);
if (statusCode != StatusCodes.Status200OK)
{
return new StatusCodeResult(statusCode);
}
_logger.LogInformation($"userId: {userId}; Negotiate Function triggered");
var negotiateResponse = await NegotiateAsync(new() { UserId = userId });
var response = JsonConvert.DeserializeObject<SignalRConnectionInfo>(negotiateResponse.ToString());
return new OkObjectResult(response);
}
}
Authenticate method is not important here because I am doing a mock of IAuthenticationManager since it ours. The problem is the NegotiateAsync since it is a protected method from ServerlessHub.
Is there a way to mock ServerlessHub? Should I create a wrapper class for it so I can just do a passthrough? What is the best solution here?
Upvotes: 1
Views: 56
Reputation: 977
Unit tests are designed for testing your application logic. But, SignalR isn't part of your application logic; it's just a transport mechanism for the data coming in and out.
So I would flip your approach around. Write your application logic in a class that doesn't rely on ServerlessHub
, so that you don't have to mock it at all.
You haven't given any information about how your application logic interacts with (or is separated from) the ServerlessHub
. But here's a fictional example that has your negotiation logic moved into a class that can be unit tested.
Let's create a class with logic that calls your authentication manager and generates some claims.
It also throws an exception if they couldn't be authenticated.
public class MyMessengerLogic
{
private readonly IAuthenticationManager _authMgr;
public MyMessengerLogic(IAuthenticationManager authMgr)
{
_authMgr = authMgr;
}
public async Task<NegotiationOptions> CreateNegotiationAsync(string userId)
{
MyUserEntity? user = await _authMgr.AuthenticateAsync(userId);
if (user == null) throw new MyAuthenticationException("Unauthenticated user");
return new()
{
UserId = user.UserId,
Claims = GenerateSomeClaims(user)
};
}
private Claims[] GenerateSomeClaims(MyUserEntity user)
{
// TODO...
}
}
[SignalRConnection("ConnectionStrings:SignalR")]
public class SignalRMessageProcessor : ServerlessHub
{
private readonly MyMessengerLogic _service;
public SignalRMessageProcessor(
IServiceProvider serviceProvider,
MyMessengerLogic service) : base(serviceProvider)
{
_service = service;
}
[Function("negotiate")]
public async Task<IActionResult> Negotiate(
[HttpTrigger("get", Route = "negotiate/{userId}")] HttpRequest req,
string userId)
{
try
{
// Anything that you want to unit test is now inside this service method:
var negotiationOptions = await _service.CreateNegotiationAsync(userId);
// From this point onwards you're just passing it to the Hub
// (this doesn't need unit testing)
var negotiateResponse = await NegotiateAsync(negotiationOptions);;
var response = JsonConvert.DeserializeObject<SignalRConnectionInfo>(negotiateResponse.ToString());
return new OkObjectResult(response);
}
catch (MyAuthenticationException aex)
{
// Catch the service exception if unauthenticated.
return new StatusCodeResult(StatusCodes.Status401Unauthorized);
}
}
}
Now that all your testable logic is in a separate class, you can unit test it quite easily... Without worrying about mocking the ServicelessHub
.
public class MessengerLogicUnitTests
{
[Fact]
public Task Negotiate_WithInvalidUser_ThrowsAuthException()
{
var authMgr = new Mock<IAuthenticationManager>();
authMgr
.Setup(f => f.AuthenticateAsync(It.IsAny<string>()))
.Returns(Task.FromResult(null));
var service = new MyMessengerLogic(authMgr.Object);
// Check an exception is thrown.
Assert.ThrowsAsync<MyAuthenticationException>(service.CreateNegotiationAsync("123"));
}
[Fact]
public async Task Negotiate_WithAuthenticatedUser_ReturnsNegotation()
{
var authMgr = new Mock<IAuthenticationManager>();
authMgr
.Setup(f => f.AuthenticateAsync("123"))
.Returns(Task.FromResult(new MyUserEntity("123", new Claim[] { /* etc */ })));
var service = new MyMessengerLogic(authMgr.Object);
var negotation = await service.CreateNegotiationAsync("123");
// Check our logic has returned some claims.
Assert.True(negotation.Claims.Any());
}
}
Upvotes: 1