With the release of dotnet core 2.1, a lot of things came together. Things like signalr and dependency injection. No more fighting the version numbers. Trying to get the exact version of signalr to work with the other exact version of dotnet runtime and so on.
Now, when things settled down, let’s get testing to work.
In this this post, I make a note to self about how to mock enough stuff to make unit testing to work.
Project here
Setup
Here, I create three projects. The web, the definitions and the repository.
Ah, and the additional Test project.
So, fire up the good old command line and type
md demo cd demo dotnet new sln --name Demo dotnet new mvc --name Demo.Web dotnet new classlib --name Demo.Definitions dotnet new classlib --name Demo.Repositories dotnet new classlib --name Demo.Models dotnet new xunit --name Demo.Tests
and now (if you are using Visual Studio 2017 proper) you would like to add them to the solution
dotnet sln add Demo.Web dotnet sln add Demo.Definitions dotnet sln add Demo.Repositories dotnet sln add Demo.Tests dotnet sln add Demo.Models
And don’t forget to reference Definitions from Repository and so on with
cd Demo.Web dotnet add reference ..\Demo.Definitions\Demo.Definitions.csproj dotnet add reference ..\Demo.Repositories\Demo.Repositories.csproj cd ..\Demo.Repositories dotnet add reference ..\Demo.Definitions\Demo.Definitions.csproj dotnet add reference ..\Demo.Models\Demo.Models.csproj cd ..\Demo.Models dotnet add reference ..\Demo.Definitions\Demo.Definitions.csproj cd ..\Demo.Tests dotnet add reference ..\Demo.Definitions\Demo.Definitions.csproj dotnet add reference ..\Demo.Models\Demo.Models.csproj dotnet add reference ..\Demo.Repositories\Demo.Repositories.csproj dotnet add reference ..\Demo.Web\Demo.Web.csproj
In this case I have a homecontroller that need the repository injected for data access and also need access to the signalr hub for push notifications to the web.
I’ll begin by creating the interfaces, models and repository.
Next, I’ll inject the repo in the HomeController. I’ll also inject the HubContext for the NotificationHub.
private readonly IRepository _repo = null; IHubContext<NotificationHub> _hubContext = null;
public HomeController(IRepository repo, IHubContext<NotificationHub> hubContext) { _repo=repo; }
For this to work we register the interface in the DI-container in Startup.cs
In the ConfigureServices method we add
services.AddTransient<IRepository, CustomerRepository>();
Testing
cd Demo.Tests dotnet add package Microsoft.Extensions.DependencyInjection -v 2.1.0 dotnet add package FluentAssertions dotnet add package Moq
For easy access to signalR and other stuff that the web project use add also the same package the webapp is using Microsoft.AspNetCore.App
dotnet add package Microsoft.AspNetCore.App -v 2.1.0
DependencyInjection and aspnetcore are interdependent and need to be of the same version.
So, In my first test I need to mock the repository
var repo = new Mock<IRepository<ICustomer>>(); repo.Setup(x => x.GetById(1)).Returns(() => { return new Customer{Id = 1, CustomerName = "Acme", CreatedOn = DateTime.Now}; });
This means that when call GetById with parameter 1, the anonymous function sent as a parameter to Returns is executed.
Furthermore, in my HomeController action UpdateCustomer I’m executing a call to the signalr hub NotificationHub.
await _hubContext.Clients.All.SendAsync("ReceiveMessage", customer);
This has to be mocked in three steps. First a mock of _hubContext that is of type IHubcontext<NotificationHub> that, when the property Clients is requested, returns an IHubClients object. This, in turn, must mock its All property to return an IClientProxy.
Since they are referencing each other we have to mock them in reverse.
var mockClientProxy = new Mock<IClientProxy>(); var mockClients = new Mock<IHubClients>(); mockClients.Setup(clients => clients.All).Returns(mockClientProxy.Object); var hub = new Mock<IHubContext<NotificationHub>>(); hub.Setup(x => x.Clients).Returns(() => mockClients.Object);
Then register these objects in the IoC container
var provider = new ServiceCollection() .AddSingleton <IRepository<ICustomer>> (repo.Object) .AddTransient<HomeController, HomeController>() .AddSingleton<IHubContext<NotificationHub>>(hub.Object) .BuildServiceProvider();
Now when I calls for my test subject, HomeController, The IoC container resolves the dependencies
var controller = provider.GetService<HomeController>();
controller.ControllerContext.HttpContext = new DefaultHttpContext(); controller.ControllerContext.HttpContext.Request.Headers.Add("Cookie", "userid=mchammer;recordno=2");
Then just to the assert
var albumc = await controller.UpdateCustomer(customer); albumc .Should() .BeAssignableTo<OkObjectResult>() .Subject.Value .Should() .BeAssignableTo<Customer>() .Subject .CustomerName .Should() .Be("Acme");
This example is admittedly quite construed (I’m testing the mock 🙂 ) but serves the purpose to show the mocking of both a signalr hub internals and then stack them together plus injecting the dependencies into the container.