Masstransit Problem adding Payload to request/response in Unit Testing, alternativly using Pipes in Unit Tests - c#

currently I have the problem that I want to write unit tests for Masstransit in .NET. My request/response consumer has some consumer filters, one of these filters are generating extra data as message payload and attaching this to the request message. In order to test my consumer in a unit test I would like to add the Payload.
Q1) Is it possible to add the payload to the request message
Q2) Alternativly, can I make a mocking filter and set it as consumer filter in the pipeline? (Which sets the payload)
This is my latest attempt:
public class ContactCommandConsumerTest
{
[Fact]
public async Task CreateContactOnUserRequestConsumer_RequestConsumer_IsAttached()
{
var harness = new InMemoryTestHarness { TestTimeout = TimeSpan.FromSeconds(5) };
[...]
var consumer = harness.Consumer<CreateContactOnUserRequestCommandConsumer>(() => new CreateContactOnUserRequestCommandConsumer(loggerConsumer, mapper,kontakteintragRep,machineTime));
var pipe = Pipe.New<PipeContext>(x => x.UseFilter(new MockFilter<PipeContext>()));
// harness.Consumer<CreateContactOnUserRequestCommandConsumer>();
await harness.Start();
try
{
harness.Bus.ConnectConsumePipe<CreateContactOnUserRequestCommandConsumer>(pipe);
var requestClient = await harness.ConnectRequestClient<CreateContactOnUserRequestCommand>();
var response = await requestClient.GetResponse<AcceptedResponse, FaultedResponse>(new
{
EntityInfo = "Vb48cc135-4593-4b96-bb29-2cf136b3d1ee",
});
Assert.True(consumer.Consumed.Select<CreateContactOnUserRequestCommand>().Any());
Assert.True(harness.Sent.Select<FaultedResponse>().Any());
}
finally
{
await harness.Stop();
}
}
}
internal class MockFilter<T> : IFilter<T> where T: class, PipeContext
{
public void Probe(ProbeContext context)
{
context.CreateFilterScope("mock");
}
public async Task Send(T context, IPipe<T> next)
{
context.GetOrAddPayload(() => new ContextUserPayload() { ContextUser = new Guid("dc6e091f-669e-45b3-9dd6-a36316f70527") });
await next.Send(context);
}
}
I tried to build a pipe and add it to "harness.bus.ConnectConsumerPipe". But the mock filter is never called ???

You use use the OnConfigureInMemoryBus event on the InMemoryTestHarness to add your filter to the bus endpoint.
Similar to:
harness.OnConfigureInMemoryBus += configurator =>
{
configurator.UseFilter(...);
}
To add a filter to the request, use:
using RequestHandle<TRequest> requestHandle = requestClient.Create(message, cancellationToken);
requestHandle.UseFilter(...);
return await requestHandle.GetResponse<TResponse>().ConfigureAwait(false);

Related

How to unit test MassTransit request response

I am trying to test a method that calls Masstransit Request
public async Task<EventsPingCompleted> SendPing()
{
_logger.LogInformation($"Sending Events Ping");
var message = new EventsPing();
var response = await _bus.Request<EventsPing, EventsPingCompleted>(message).ConfigureAwait(false);
return response.Message;
}
I want to return a specific response as part of my unit test. To do this I created a stubbed consumer because I don't want to unit test the consumer, I am doing that in other tests.
public class StubbedConsumer<T,TRt>: IConsumer<T> where T : class where TRt : class
{
private readonly TRt _response;
public StubbedConsumer( TRt expectedResponse )
{
_response = expectedResponse;
}
public async Task Consume(ConsumeContext<T> context)
{
await context.RespondAsync<TRt>(_response).ConfigureAwait(false);
}
}
My unit test is passing but when I debug it the stubbed consumer is being called in my unit test.
[Fact]
public async Task SendPing_GetResponse()
{
// Arrange
var harness = new InMemoryTestHarness();
var expectedResult = new EventsPingCompleted();
var startEvent = new EventsPing();
// the harness needs a consumer for request to work
harness.Consumer(() => new StubbedConsumer<EventsPing, EventsPingCompleted>(expectedResult));
await harness.Start();
// helper needs to be after harness is started
var helper = new PingHelper(harness.BusControl, _logger.Object);
//connect a request client
var requestClient = await harness.ConnectRequestClient<EventsPing>().ConfigureAwait(false);
// Act
// this configures the response, these 2 are equivalent but without calling get response on the request client the tests time out
// when both are called the StubbedConsumer is called twice
var response = await requestClient.GetResponse<EventsPingCompleted>(startEvent);
var result = await helper.SendPing().ConfigureAwait(false);
try
{
// Assert
Assert.True(harness.Sent.Select<EventsPing>().Any());
Assert.True(harness.Consumed.Select<EventsPingCompleted>().Any());
Assert.Equal(expectedResult.Time, result.Time);
Assert.Equal(expectedResult.Time, response.Message.Time);
}
finally
{
await harness.Stop();
}
}
The first message is dispatched by
var response = await requestClient.GetResponse<EventsPingCompleted>(startEvent);
And then the second happens in my system under test
var result = await helper.SendPing().ConfigureAwait(false);
If I remove the call requestClient.GetResponse() the SUT times out without receiving the response from the stubbed consumer.
Is there a way that I can configure the request client without calling GetResponse so that I'm not sending 2 events?

Mock handler with parameter Mediatr and Moq

I'm trying to mock a handler with Moq. This handler takes a parameter of type bool to filter out active customers and non active customers.
The handler is used in my service:
public async Task<IEnumerable<CustomerDto>> GetCustomers(bool active)
{
return _mapper.Map<IEnumerable<CustomerDto>>(await _mediatr.Send(new GetCustomersQuery { Active = active }));
}
My handler looks like this:
public class GetCustomersHandler : IRequestHandler<GetCustomersQuery, IEnumerable<Customer>>
{
private readonly ICustomersRepository _repository;
public GetCustomersHandler(ICustomersRepository repository)
{
_repository = repository;
}
public async Task<IEnumerable<Customer>> Handle(GetCustomersQuery request, CancellationToken cancellationToken)
{
return await _repository.GetCustomers(request.Active);
}
}
My test:
[Fact]
public async Task CustomersService_GetCustomers_ActiveReturnsOnlyActiveCustomers()
{
var result = await _service.GetCustomers(true);
// asserts to test result
}
My mock code:
var mockMediatr = new Mock<IMediator>();
mockMediatr.Setup(m => m.Send(It.IsAny<GetBlockedCustomersAndGroupsQuery>(), It.IsAny<CancellationToken>()))
.Returns(async (bool active) =>
await _getBlockedCustomersAndGroupsHandler.Handle(new GetBlockedCustomersAndGroupsQuery { Active = active }, new CancellationToken())); ---> How can I pass my bool parameter here?
EDIT:
I don't have the mock code for the mediator in my test (for reuse). I want to be able to test both scenarios where true is passed and false is passed. If I try it like mentioned above, I get this error: "Invalid callback. Setup on method with 2 parameter(s) cannot invoke callback with different number of parameters (1)".
I can mock the mediator in the test code and pass that:
mockMediatr.Setup(m => m.Send(It.IsAny<GetBlockedCustomersAndGroupsQuery>(), It.IsAny<CancellationToken>()))
.Returns(async () =>
await _getBlockedCustomersAndGroupsHandler.Handle(new GetBlockedCustomersAndGroupsQuery { Active = true }, new CancellationToken()));
But here I'm not able to reuse it in the second test (with Active = false) and I have some duplicated code. Is there a way to do it like this or do I need to put the mock code inside the test code?
I found how I can access the data that is passed.
mockMediatr.Setup(m => m.Send(It.IsAny(), It.IsAny())) .Returns(async (GetBlockedCustomersAndGroupsQuery q, CancellationToken token) => await _getBlockedCustomersAndGroupsHandler.Handle(new GetBlockedCustomersAndGroupsQuery { Active = q.Active}, token));

MassTransit Consumer Test Passing But Confusing Error Thrown

I'm trying to Unit test a MassTransit Consumer using the MassTransit.Testing Framework and the InMemoryTestHarness.
I'm able to successfully test that a message is sent for two separate consumers so far.
One of the consumers is also successfully consumed, but I get an error message as follows:
R-FAULT loopback://localhost/vhost/input_queue 49820000-5689-0050-3b5c-08d5ecc4708c Acme.Company.Messages.Commands.ISomeCommand Acme.Company.SomeService.Consumers.SomeCommandConsumer(00:00:00.2328493) Failure: The payload was not found: MassTransit.RabbitMqTransport.ModelContext, StackTrace: at GreenPipes.PipeExtensions.GetPayload[TPayload](PipeContext context) at MassTransit.DeferExtensions.Defer[T](ConsumeContext1 context, TimeSpan delay, Action2 callback)
The code at this point is attempting to defer the message for one minute so I wonder whether that is the reason for the missing payload???
The code is as follows:
[TestFixture]
public class SomeCommandConsumerTests
{
private InMemoryTestHarness _harness;
private Mock<ISomeRepository> _SomeRepositoryMock;
private Mock<IAnotherRepository> _AnotherRepositoryMock;
[OneTimeSetUp]
public async Task OneTimeInit()
{
_harness = new InMemoryTestHarness("vhost");
_harness.Consumer(() => new SomeCommandConsumer(_SomeRepositoryMock.Object, _AnotherRepositoryMock.Object));
await _harness.Start();
}
[SetUp]
public void Init()
{
_SomeRepositoryMock = new Mock<ISomeRepository>();
_AnotherRepositoryMock = new Mock<IAnotherRepository>();
_SomeRepositoryMock.Setup(x => x.UpdateSomeId(It.IsAny<SomeEnum>(), It.IsAny<int>()))
.Returns(Task.Factory.StartNew(() => { }));
_SomeRepositoryMock.Setup(x => x.UpdateProcMessage(It.IsAny<string>(), It.IsAny<int>()))
.Returns(Task.Factory.StartNew(() => { }));
_SomeRepositoryMock.Setup(
x => x.UpdateSomeProcStartTime(It.IsAny<int>()))
.Returns(Task.Factory.StartNew(() => { }));
_SomeRepositoryMock.Setup(
x => x.UpdateSomeProcEndTime(It.IsAny<int>()))
.Returns(Task.Factory.StartNew(() => { }));
}
[Test]
public async Task ProcessMessage_MethodCalledWithSomeCondition_MessageSent()
{
//Arrange
_SomeRepositoryMock.Setup(x => x.GetAsync(It.IsAny<int>())).ReturnsAsync(new Entity
{
Property1 = true,
SomeID = 12345
});
await _harness.InputQueueSendEndpoint.Send(new SomeCommand
{
MessageType = MessageTypeEnum.SomeMessgae,
SomeID = 12345
});
//Assert
_harness.Sent.Select<ISomeCommand>().Any().Should().BeTrue();
}
[Test]
public async Task ProcessMessage_MethodCalledWithSomeCondition_CorrectNextStepReturned()
{
//Arrange
_SomeRepositoryMock.Setup(x => x.GetAsync(It.IsAny<int>())).ReturnsAsync(new Control()
{
Property1 = true,
SomeID = 12345
});
await _harness.InputQueueSendEndpoint.Send(new SomeCommand
{
MessageType = MessageTypeEnum.SomeMessgae,
SomeID = 12345
});
//Assert
_harness.Consumed.Select<ISomeCommand>().Any().Should().BeTrue();
_harness.Consumed
.Select<ISomeCommand>()
.First()
.Context
.Message
.SomeID
.Should()
.Be(12345);
_harness.Consumed
.Select<ISomeCommand>()
.First()
.Context
.Message
.MessageProcessingResult
.Should()
.Be(MessageProcessingResult.DeferProcessing);
}
[OneTimeTearDown]
public async Task Teardown()
{
await _harness.Stop();
}
}
And the code that is being hit in the consumer is:
await context.Defer(TimeSpan.FromMinutes(1));
Basically, what am I missing, is it even an issue?
This is happening because you are using the in-memory test harness with a feature (Defer) that is supported by RabbitMQ. Defer tries to use the RabbitMQ model from the consumer to defer the message, and it isn't there, because in-memory doesn't know anything about it.
If you want to use a more generic solution, use Redeliver instead. You'll need to use the QuartzIntegration library with the in-memory test harness, but it does in-memory message redelivery using that scheduler.
You'll also need to update your RabbitMQ bus configuration to include the cfg.UseDelayedExchangeMessageScheduler(); so that RabbitMQ is used for message scheduling.

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.

How could I Mock this code?

I want to start mocking some code but I am unsure how to do it. I have read a few tutorials but I cannot apply it to my code (There maybe a reason behind it).
I am using NUnit and Moq.
(I have removed all other methods so I can just show you one).
Api Interface:
public interface IApiRequest
{
Task<T> ExecuteAsync<T>(RestRequest request);
}
Method I want to Mock:
public async Task<UpcomingMovies> GetUpcomingMovies(int page)
{
var request = new RestRequest
{
Resource = "movie/upcoming",
};
request.AddParameter("page", page.ToString());
request.AddParameter("language", "en");
var api = new ApiRequest();
return await api.ExecuteAsync<UpcomingMovies>(request);
}
I'm not sure how I can mock this.
Update:
Is this now a valid test?
Mock<IApiRequest> mock = new Mock<IApiRequest>();
mock.Setup(x => x.ExecuteAsync<UpcomingMovies>(It.IsAny<RestRequest>()))
.Returns(Task.FromResult<UpcomingMovies>(new UpcomingMovies()));
If you want to mock the ExecutAsync method you can do it like this:
Mock<IApiRequest> mock = new Mock<IApiRequest>();
mock.Setup(x => x.ExecuteAsync<UpcomingMovies>(It.IsAny<RestRequest>()))
.Returns(Task.FromResult<UpcomingMovies>(/** whatever movies **/));
if you want to mock for a particlur request, replace It.IsAny<RestRequest>() with a reference to your request.
To effectively test your class you need something like this:
public class MyClass
{
public MyClass(IApiRequest api)
{
this.api = api;
}
public async Task<UpcomingMovies> GetUpcomingMovies(int page)
{
var request = new RestRequest
{
Resource = "movie/upcoming",
};
request.AddParameter("page", page.ToString());
request.AddParameter("language", "en");
return await api.ExecuteAsync<UpcomingMovies>(request);
}
}
Test
[Test]
public async Task MyTest()
{
var expectedMovies = new UpcomingMovies(); // or whatever movies you need
Mock<IApiRequest> mock = new Mock<IApiRequest>();
mock.Setup(x => x.ExecuteAsync<UpcomingMovies>(It.IsAny<RestRequest>()))
.Returns(Task.FromResult<UpcomingMovies>(expectedMovies));
var myClass = new MyClass(mock.Object);
var result = await myClass.GetUpcomingMovies(1);
Assert.IsTrue(expectedMovies == result);
}

Categories

Resources