How to mock MassTransit Header values in unit test using NSubstitute - c#

I'm trying to create a unit test that will test my MassTransit (RabbitMq) consumer. My consumer is expected a bus header key called UserName. I've simplified my consumer class as follows. I need to find a way to mock the bus header data otherwise the test will always raise an exception when it executes context.Headers.TryGetHeader("UserName", out object value)
I'm using the NSubstitute mocking library. So how do I mock the ConsumeContext<MyDataObject> type, and set a mock header value? I prefer a solution with NSubstitute
public class MyDataObjectCommand
{
public string MyProperty { get; set; }
}
// Consumer class
public class MyConsumer : IConsumer<MyDataObjectCommand>
{
private readonly IHubContext<MessageHub> _hubContext;
public MyConsumer(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
public async Task Consume(ConsumeContext<MyDataObjectCommand> context)
{
var myProperty = context.Message.MyProperty;
var userName = TryGetHeader(context, "UserName");
DoSomethingWith(_hubContext, myProperty, userName);
}
private string TryGetHeader(ConsumeContext<MyDataObjectCommand> context, string key, bool errorOnNull = true)
{
if (context.Headers.TryGetHeader(key, out object value))
{
return Convert.ToString(value);
}
if (errorOnNull)
{
throw new MyConsumerException(_reportName, $"{key} is not found", _hubContext);
}
return null;
}
}
// Unit Test (I'm using XUnit)
public class MyConsumerTests
{
private readonly IHubContext<MessageHub> _hubContext;
public MyConsumerTests()
{
_hubContext = Substitute.For<IHubContext<MessageHub>>();
}
[Fact]
public async Task ShouldConsume()
{
// arrange
var mockDataObject = new MyDataObjectCommand() { MyProperty = "x" };
var harness = new InMemoryTestHarness();
harness.Consumer(() => new MyConsumer(_hubContext));
// I need to mock header values on ConsumeContext<MyDataObjectCommand> object here
// but how do I do it using NSubstitute?
// context.Headers["UserName"] = "Bob"; ??
await harness.Start();
try
{
// act
await harness.InputQueueSendEndpoint.Send(mockDataObject);
// assert
Assert.True(await harness.Consumed.Any<MyDataObjectCommand>());
Assert.False(await harness.Consumed.Any<Fault<MyDataObjectCommand>>());
//Assert.Equal(context.Header["UserName"], "Bob"); How do I do the assertion??
}
finally
{
await harness.Stop();
}
}
}

You don’t mock header values when using MassTransit, you just have to set them when you’re sending the message using the test harness. There is absolutely zero need for a mocking framework.
await harness.InputQueueSendEndpoint.Send(mockDataObject,
context => context.Headers.Set(“UserName”, “Bob”));
The header will then be available on the ConsumeContext when the message is delivered to the consumer.

Related

How to do integration test with MediatR using Moq in C#?

I am new to Integration test, I have a controller which has IMediator and I am using Moq framework for the integration test.
The issue I am having is that I keep getting null in the response when trying to mock MediatR. Before trying to mock MediatR, I tried to mock a service (in this case IUserService) and it worked perfectly (the return type for the Delete controller and other methods was bool).
Since I am now using IMediator in the controller so trying to change the integration test to mock MediatR, I can do integration test for the handler which has IUserService but I am trying to test the whole pipeline. Below is what I have in terms of code starting from the controller to the integration test.
//Controller
private IMediator _mediator;
public UserController(IMediator mediator)
{
_mediator = mediator;
}
[HttpDelete("{id}")]
public async Task<ActionResult<Result<Unit>>> DeleteUser(int id)
{
return await _mediator.Send(new DeleteUserCommand { UserId = id });
}
public class DeleteUserCommand : IRequest<Result<Unit>> {public int UserId { get; set; }}
//Command handler
public class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand, Result<Unit>>
{
private readonly IUserService _userService;
public DeleteUserCommandHandler(IUserService userService)
{
_userService = userService;
}
public async Task<Result<Unit>> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
return await _userService.DeleteUser(request.UserId);
}
}
//Service layer
public async Task<Result<Unit>> DeleteUser(int userId)
{
var userExist = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (userExist == null) return Result<Unit>.Failure("User Id doesn't exsist");
_context.Remove(userExist);
var result = await _context.SaveChangesAsync() > 0;
return Result<Unit>.Success(Unit.Value);
}
//Integration test
[TestFixture]
public class UserControllerTests
{
private readonly Mock<IMediator> _mockMediator;
private UserController _userController;
public UserControllerTests()
{
_mockMediator = new Mock<IMediator>();
}
[Test]
public async Task DeleteUser_NotSuccessful_NoIdDeleted()
{
Result<Unit> expected = new Result<Unit>();
_mockMediator.Setup(x => x.Send(It.IsAny<DeleteUserCommand>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(expected));
_userController = new UserController(_mockMediator.Object);
var result = await _userController.DeleteUser(6);
Assert.AreEqual("User Id doesn't exsist", result?.Value?.Error);
}
}
//Response in the integration test
I have two UserIds 6 and 7
UserId 6: doesn't exsist so I am expecting a message saying id doesn't exsist but the current response I am getting is null.
UserId 7: does exsist and expecting something like IsSuccess: true which is a custom code I added
Note: the attached code for the test is just for user Id 6.
You might notice in the code above starting from the controller, part of the return type is Result which is a custom class I added and below is the code.
public class Result<T>
{
public bool IsSuccess { get; set; }
public T Value { get; set; }
public string Error { get; set; }
public static Result<T> Success(T value) => new Result<T> { IsSuccess = true, Value = value };
public static Result<T> Failure(string error) => new Result<T> { IsSuccess = false, Error = error };
}
`
I am trying to find out what I have done wrong with mocking MediatR and why it keep returning null.
Thanks for the help in advance.
First thing I tried to do integration test mock the IUserService then switched to IMediator then started to get null in the response when doing integration test. I tried to google the issue but no luck.

Unit testing a c# Method using Moq

I'm new to unit testing and the language c sharp and I'm trying to unit test by mocking ISessionStorageService to test the HasGlobaladminaccess function but my setup does not seem to work as expected.
public class Context
{
public asyncTask<bool>HasGlobalAdminAccessAsync(Blazored.SessionStorage.ISessionStorageService sessionStorage)
{
return await HasAccessAsync(sessionStorage, Config.Constants.HAS_GLOBALE_ADMIN_ACCESS);
}
private async Task<bool> HasAccessAsync(Blazored.SessionStorage.ISessionStorageService sessionStorage, string sessionStorageKey)
{
ILogger _logger = Log.ForContext<ContextHelpers>();
try
{
_logger.Debug("HasAccessAsync({sessionStorageKey})", sessionStorageKey);
var access = await sessionStorage.GetItemAsync<bool>(sessionStorageKey);
_logger.Debug("HasAccessAsync({sessionStorageKey})::access={access}", sessionStorageKey, access);
return access;
}
catch (Exception ex)
{
_logger.Debug("HasAccessAsync({sessionStorageKey})::exception={ex}", sessionStorageKey, ex.Message);
return false;
}
}
}
My test method
private readonly Mock<ISessionStorageService> MockStorage = new Mock<ISessionStorageService>();
[Fact()]
public async Task HasGlobalAdminAccessAsyncTest()
{
string guid = System.Guid.NewGuid().ToString();
Context context = new Context();
MockStorage.Setup(foo => foo.GetItemAsync<bool>(guid)).ReturnsAsync(true);
var person = await context.HasGlobalAdminAccessAsync(MockStorage.Object);
Assert.True(person , guid);
}
Either use the same value as what's in Config.Constants.HAS_GLOBALE_ADMIN_ACCESS for the GUID value in your test
Or use
foo => foo.GetItemAsync<bool>(It.IsAny<Guid>())
In your Setup call.
As it's written, you have your test set to only accept the exact GUID that is returned from Guid.NewGuid() in your test, so it's not going to work in general.

How to mock / unit test HTTP Client - restease

tl;dr: I'm having trouble mocking restease**
Also, I realize I may be totally on the wrong track, so any suggestions / nudges in the right direction would be of great help. I am quite new to this.
I'm making a small HTTP Client library, built around RestEase. RestEase is nice and easy to use, but I'm having trouble mocking the calls for the purpose of unit testing.
I want to use moq and NUnit, but I can't properly mock the RestClient. Example (shortened for brevity):
IBrandFolderApi - interface needed by restease to send calls
public interface IBrandFolderApi
{
[Post("services/apilogin")]
Task<LoginResponse> Login([Query] string username, [Query] string password);
}
BrandfolderClient.cs - the main class
public class BrandfolderClient : IBrandfolderClient
{
private IBrandFolderApi _brandFolderApi { get; set; }
public BrandfolderClient(string url)
{
_brandFolderApi = RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password)
{
LoginResponse loginResponse = await _brandFolderApi .Login(username, password);
if (loginResponse.LoginSuccess)
{
....
}
....
return loginResponse.LoginSuccess.ToString();
}
}
The unit tests
public class BrandFolderTests
{
BrandfolderClient _brandfolderClient
Mock<IBrandFolderApi> _mockBrandFolderApii;
[SetUp]
public void Setup()
{
//The test will fail here, as I'm passing a real URL and it will try and contact it.
//If I try and send any string, I receive an Invalid URL Format exception.
string url = "https://brandfolder.companyname.io";
_brandfolderClient = new BrandfolderClient (url);
_mockBrandFolderApii= new Mock<IBrandFolderApi>();
}
....
}
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
The test is failing at the constructor - if I send a valid URL string, then it will send a call to the actual URL. If I send any other string, I get an invalid URL format exception.
I believe I haven't properly implemented something around the rest client, but I'm not sure where. I'm very stuck on this, I've been googling and reading like crazy, but I'm missing something and I don't know what.
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
You actually should not have any need to mock RestClient.
Refactor your code to depend explicitly on the abstraction you control
public class BrandfolderClient : IBrandfolderClient {
private readonly IBrandFolderApi brandFolderApi;
public BrandfolderClient(IBrandFolderApi brandFolderApi) {
this.brandFolderApi = brandFolderApi; //RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password) {
LoginResponse loginResponse = await brandFolderApi.Login(username, password);
if (loginResponse.LoginSuccess) {
//....
}
//....
return loginResponse.LoginSuccess.ToString();
}
}
removing the tight coupling to static 3rd party implementation concerns will allow your subject to be more explicit about what it actually needs to perform its function.
This will also make it easier for the subject to be tested in isolation.
For example:
public class BrandFolderTests {
BrandfolderClient subject;
Mock<IBrandFolderApi> mockBrandFolderApi;
[SetUp]
public void Setup() {
mockBrandFolderApi = new Mock<IBrandFolderApi>();
subject = new BrandfolderClient(mockBrandFolderApi.Object);
}
//....
[Test]
public async Task LoginTest() {
//Arrange
LoginResponse loginResponse = new LoginResponse() {
//...
};
mockBrandFolderApi
.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(loginResponse);
//Act
string response = await subject.Login("username", "password");
//Assert
mockBrandFolderApi.Verify(x => x.Login(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
}
In production code, register and configure the IBrandFolderApi abstraction with the container, applying what ever 3rd party dependencies are required
Startup.ConfigureServices
//...
ApiOptions apiOptions = Configuration.GetSection("ApiSettings").Get<ApiOptions>();
services.AddSingleton(apiOptions);
services.AddScoped<IBrandFolderApi>(sp => {
ApiOptions options = sp.GetService<ApiOptions>();
string url = options.Url;
return RestClient.For<IBrandFolderApi>(url);
});
Where ApiOptions is used to store settings
public class ApiOptions {
public string Url {get; set;}
//... any other API specific settings
}
that can be defined in appsetting.json
{
....
"ApiSettings": {
"Url": "https://brandfolder.companyname.io"
}
}
so that they are not hard coded all over you code.
The HttpClient comes from System.Net.Http, which is not easy to mock.
You can, however, create a test HttpClient by passing a fake HttpMessageHandler. Here is an example:
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
You can create create a test instance of HttpClient as shown below:
var httpClient = new HttpClient(new FakeHttpMessageHandler(true))
{ BaseAddress = new Uri("baseUrl") };
Not sure how you are using verify on _httpClient, its not a mock. but what you are looking for is https://github.com/canton7/RestEase#custom-httpclient. Most people pass in factory for this
//constructor
public httpClientConstructor(string url, IHttpHandlerFactory httpHandler)
{
var httpClient = new HttpClient(httpHandler.GetHandler())
{
BaseAddress = new Uri(url),
};
_exampleApi = RestClient.For<IExampleApi>(url);
}
public interface IHttpHandlerFactory<T>
{
T GetHandler() where T: HttpMessageHandler
}
Thanks Ankit Vijay https://stackoverflow.com/a/68240316/5963888
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
[SetUp]
public void Setup()
{
var fakeHandler = new Mock<IHttpHandlerFactory>();
fakeHandler.Setup(e => e.GetHandler() ).Returns( new FakeHttpHandler() );
_httpClient = new HttpClient(fakeHandler.Object);
_exampleApi = new Mock<IExampleApi>();
}

Controller API Testing with xUnit/Moq - Controller is null

I'm new to unit testing, so my problem is probably with my code and not the Moq framework, but here goes.
I'm using .Net Core with xUnit and the Moq framework, and I'm more or less following instructions from their documentation. I'm trying to test route api/user to get all users, and the issue was on asserting that the response was an ObjectResult containing <IEnumerable<User>>. No matter what I tried, result.Value was always null. The first assertion passes fine.
I set up a console project to debug this, and found something interesting. that value of the controller in the test method in Visual Studio is null. In VS Code, the value in the debugger shows Unknown Error: 0x00000....
Below is the test:
public class UserControllerTests {
[Fact]
public void GetAll_ReturnsObjectResult_WithAListOfUsers() {
// Arrange
var mockService = new Mock<IUserService>();
var mockRequest = new Mock<IServiceRequest>();
mockService.Setup(svc => svc.GetUsers(mockRequest.Object))
.Returns(new ServiceRequest(new List<User> { new User() }));
var controller = new UserController(mockService.Object);
// Act
var result = controller.GetAll();
// Assert
Assert.IsType<ObjectResult>(result);
Assert.IsAssignableFrom<IEnumerable<User>>(((ObjectResult)result).Value);
}
}
And here is the controller:
public class UserController : Controller {
private IUserService service;
public UserController(IUserService service) {
this.service = service;
}
[HttpGet]
public IActionResult GetAll() {
var req = new ServiceRequest();
service.GetUsers(req);
if(req.Errors != null) return new BadRequestObjectResult(req.Errors);
return new ObjectResult(req.EntityCollection);
}
}
And the Service Layer:
public interface IUserService {
IServiceRequest GetUsers(IServiceRequest req);
}
public class UserService : IUserService {
private IUserRepository repo;
public IServiceRequest GetUsers(IServiceRequest req) {
IEnumerable<User> users = null;
try {
users = repo.GetAll();
}
catch(MySqlException ex) {
req.AddError(new Error { Code = (int)ex.Number, Message = ex.Message });
}
finally {
req.EntityCollection = users;
}
return req;
}
}
public interface IServiceRequest {
IEnumerable<Object> EntityCollection { get; set; }
List<Error> Errors { get; }
void AddError(Error error);
}
public class ServiceRequest : IServiceRequest {
public IEnumerable<Object> EntityCollection { get; set; }
public virtual List<Error> Errors { get; private set; }
public ServiceRequest () { }
public void AddError(Error error) {
if(this.Errors == null) this.Errors = new List<Error>();
this.Errors.Add(error);
}
}
Like I said, it's probably something I'm doing wrong, I'm thinking in the mockService.Setup() but I'm not sure where. Help please?
From the use of service.GetUsers(req) it looks like service is suppose to populate the service request but in your setup you have it returning a service request. A result which is also not used according to your code.
You need a Callback to populate whatever parameter is given to the service in order to mock/replicate when it is invoked. Since the parameter is being created inside of the method you will use Moq's It.IsAny<> to allow the mock to accept any parameter that is passed.
var mockService = new Mock<IUserService>();
mockService.Setup(svc => svc.GetUsers(It.IsAny<IServiceRequest>()))
.Callback((IServiceRequest arg) => {
arg.EntityCollection = new List<User> { new User() };
});
This should allow the method under test to flow through it's invocation and allow you to assert the outcome.

ASP.NET MVC WebAPI create ViewModel from async tasks

I write web application using ASP.NET MVC WebAPI and I want to transform current synchronous code to asynchronous for optimization. Problem is that I fill ViewModel with multiple objects taken from repository. These calls from repository should be async.
Let's asume I have signature for repository calls respecting this interface
public interface ICompanyRepository
{
IEnumerable<Company> GetCompanies();
IEnumerable<Address> GetAddresses();
}
ViewModels definition
public class CompaniesFullViewModel
{
public IEnumerable<Company> Companies { get; set; }
public IEnumerable<Address> Addresses { get; set; }
}
And controller:
public class CompanyController
{
public readonly ICompanyRepository Repository { get; private set; }
public CompanyController(IRepository repository)
{
Repository = repository;
}
[ResponseType(typeof(CompaniesFullViewModel))]
public HttpResponseMessage Get()
{
var companies = Repository.GetCompanies();
var addresses = Repository.GetAddresses();
HttpStatusCode statusCode = companies.Any()
? HttpStatusCode.OK
: HttpStatusCode.PartialContent;
return
Request.CreateResponse(
statusCode,
new CompaniesFullViewModel
{
Companies = companies,
Addresses = addresses
});
}
}
Furthermore I have tests implemented to the controller:
[TestClass]
public sealed class CompanyTestController : BaseTestController
{
#region Fields
private static Mock<ICompanyRepository> _repositoryMock;
private static CompanyController _controller;
#endregion
[ClassInitialize]
public static void Initialize(TestContext testContext)
{
// Mock repository
_repositoryMock = new Mock<ICompanyRepository>();
DependencyResolver.Default.Container.RegisterInstance(_repositoryMock.Object);
// Create controller
_controller =
DependencyResolver.Default.Container.Resolve<CompanyController>();
// Init request
_controller.Request = new HttpRequestMessage();
_controller.Request.SetConfiguration(new HttpConfiguration());
}
[ClassCleanup]
public static void Cleanup()
{
_controller.Dispose();
}
[TestMethod]
public void Get_ActionExecutes_ReturnsEmptyCompaniesViewModel()
{
var companies = new List<Company>();
var addresses = new List<Address>();
// Setup fake method
_repositoryMock
.Setup(c => c.GetCompanies())
.Returns(companies);
_repositoryMock
.Setup(c => c.GetAddresses())
.Returns(addresses);
// Execute action
var response = _controller.Get();
// Check the response
Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
}
}
How can I convert the controller to async, if the repository is async and the signature looks like this:
public interface ICompanyRepository
{
Task<IEnumerable<Company>> GetCompaniesAsync();
Task<IEnumerable<Address>> GetAddressesAsync();
}
What you need to do is change the Controller action to be async as well, and change the return type to Task<>. You can then await your asynchronous repository calls:
[ResponseType(typeof(CompaniesFullViewModel))]
public async Task<HttpResponseMessage> Get() // async keyword.
{
var companies = await Repository.GetCompaniesAsync(); // await
var addresses = await Repository.GetAddressesAsync(); // await
HttpStatusCode statusCode = companies.Any()
? HttpStatusCode.OK
: HttpStatusCode.PartialContent;
return
Request.CreateResponse(
statusCode,
new CompaniesFullViewModel
{
Companies = companies,
Addresses = addresses
});
}
By convention, you can also change the name of the controller action to end in Async as well, although if you are using RESTful conventions and / or Routing attributes, the actual name of the controller action isn't really important.
Testing
I use XUnit and NUnit, but it seems MSTest also supports testing of asynchronous methods, and Moq also provides Async versions of the setups:
[Test]
public async Task Get_ActionExecutes_ReturnsEmptyCompaniesViewModel() // async Task
{
var companies = new List<Company>();
var addresses = new List<Address>();
// Setup fake method
_repositoryMock
.Setup(c => c.GetCompaniesAsync())
.ReturnsAsync(companies); // Async
_repositoryMock
.Setup(c => c.GetAddressesAsync())
.ReturnsAsync(addresses); // Async
// Execute action
var response = await _controller.Get(); // Await
// Check the response
Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
_repositoryMock.Verify(m => m.GetAddressesAsync(), Times.Once);
_repositoryMock.Verify(m => m.GetCompaniesAsync(), Times.Once);
}
As an aside, it seems you are using Setter Dependency injection. An alternative is to use Constructor injection, which has the benefit of ensuring that the class is always in a valid state (i.e. there is no transient state while it is waiting for the dependencies to be set). This also allows the dependencies (your repository in this case) to be made readonly.

Categories

Resources