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));
Related
I have the following code which reads a Transaction from Kafka, and updates the account balance to show that transaction
public class KafkaConsumerService : BackgroundService
{
private readonly IConsumer<string, Transaction> _kafkaConsumer;
private readonly IRepository _repository;
private readonly ICalculator _calculator;
public KafkaConsumerService(
IConsumer<string, Transaction> kafkaConsumer,
IRepository repository,
ICalculator calculator
)
{
_kafkaConsumer = kafkaConsumer;
_repository = repository;
_calculator = calculator;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var consumeResult = await Task.Run(() => _kafkaConsumer.Consume(stoppingToken), stoppingToken);
var transaction = consumeResult.Message.Value;
var account = await _repository.GetAccount(transaction.Account);
await _repository.UpdateAccount(_calculator.CalculateAccount(account, Normalize(transaction)));
}
private Transaction Normalize(Transaction transaction)
{
if (!transaction.IsCancellation)
{
return transaction;
}
return new Transaction(transaction)
{
Amount = transaction.Amount * -1,
IsCancellation = false
};
}
}
I have then written the following unit test for this, using XUnit and Moq
public class KafkaConsumerServiceTest
{
private readonly Mock<IConsumer<string, Transaction>> _kafka = new();
private readonly Mock<IRepository> _repository = new();
private readonly Mock<ICalculator> _calculator = new();
private readonly Fixture _fixture = new();
private readonly KafkaConsumerService _kafkaConsumerService;
public KafkaConsumerServiceTest()
{
_kafkaConsumerService = new KafkaConsumerService(_kafka.Object, _repository.Object, _calculator.Object);
}
[Fact]
public async Task KafkaConsumerService_ProcessesCancelationTransaction()
{
_fixture.Customize<Transaction>(composer => composer
.With(transaction => transaction.IsCancellation, true)
);
var transaction = _fixture.Create<Transaction>();
_kafka
.Setup(consumer => consumer.Consume(It.IsAny<CancellationToken>()))
.Returns(new ConsumeResult<string, Transaction>
{
Message = new Message<string, Transaction>
{
Value = transaction,
},
});
var result = _fixture.Create<Account>() with
{
AccountName = transaction.Account
};
_repository
.Setup(repository => repository.GetAccount(transaction.Account))
.ReturnsAsync(result);
_calculator
.Setup(calculator => calculator.CalculateAccount(It.IsAny<Account?>(), It.IsAny<Transaction>()))
.Returns(result);
await _kafkaConsumerService.StartAsync(CancellationToken.None);
_repository.Verify(repository =>
repository.GetAccount(transaction.Account)
);
_calculator.Verify(calculator =>
calculator.CalculateAccount(result, transaction)
);
_repository.Verify(repository => repository.UpdateAccount(result));
}
}
However I then get the following error
Moq.MockException
Expected invocation on the mock at least once, but was never performed: repository => repository.GetAccount("Account73ccea18-e39c-493f-9533-7af7f983b8ab")
Performed invocations:
Mock<IRepository:1> (repository):
IRepository.GetAccount("Account73ccea18-e39c-493f-9533-7af7f983b8ab")
IRepository.UpdateAccount(Account { AccountName = Account73ccea18-e39c-493f-9533-7af7f983b8ab, Amount = 119 })
As you can see it says the method GetAccount("Account73ccea18-e39c-493f-9533-7af7f983b8ab") was never called, however right below it under Performed invocations, it says it was called.
If anyone has any ideas as to what is going wrong here I would appreciate it.
EDIT
Adding an await Task.Delay(100) on the unit tests seem to fix the problem, however this isnt an ideal solution, and I still dont understand why the issue occurs in the first place.
EDIT #2
It seems that removing the extension of BackgroundService (https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice?view=dotnet-plat-ext-7.0) seems to fix the test aswell. Could this somehow be causing a race condition in my code?
I think the culprit could be this:
return new Transaction(transaction)
{
Amount = transaction.Amount * -1,
IsCancellation = false
};
When you verify against an instance, it is doing a reference check so it cannot be a different newly made object.
Try
_repository.Verify(repository =>
repository.GetAccount(It.IsAny<string>())
);
_repository.Verify(repository => repository.UpdateAccount(It.IsAny<Transaction>()));
You may also use It.Is<Transaction>(t => t.AccountName == "account") to validate specific values in the assertion.
Looking into this further. It appears that BackgroundService.StartAsync will call ExecuteAsync, and then return Task.CompletedTask
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
This then meant that my code has not finished executing yet, hence why my moq assertions failed. I then assume that the timing between these two was very close, so that by the time the error was generated, the methods had been called hence the confusing error message.
I fixed this issue by simply waiting for the executed task to be completed
await _kafkaConsumerService.StartAsync(CancellationToken.None);
await _kafkaConsumerService.ExecuteTask;
Using Moq 4.18.1 to test my C# code, I'm wanting to verify a method calls another method. In the class being tested I have this:
public class IntegratedPlatformRepository : PvRepository<OutputDTO>, IIntegratedPlatformRepository
{
protected virtual async Task IpSpecificGetterExtras(OutputDTO dto, CancellationToken cancellationToken) { }
public async Task<OutputDTO> GetAsync(Guid uid, CancellationToken cancellationToken = default) {
var dto = ...
await IpSpecificGetterExtras(dto, cancellationToken);
...
}
I want to ensure that IpSpecificGetterExtras is called when GetAsync is called, so I tried this:
[Fact]
public async Task GetAsync_Calls_IpSpecificGetterExtras()
{
// Arrange
var mock = new Mock<IntegratedPlatformRepository> {
CallBase = true
};
// Act
await _repo.GetAsync(Guid.NewGuid().ToString());
// Assert
mock
.Protected()
.Verify<Task>(
"IpSpecificGetterExtras", Times.Once(),
ItExpr.IsAny<OutputDTO>(), ItExpr.IsAny<CancellationToken>()
);
}
If I simply run the test it fails saying the invocation wasn't performed. If I debug the test and set a breakpoint in IpSpecificGetterExtras the breakpoint is hit and I can step through the method, so it's definitely being called.
Based on the shown example mock was not invoked.
The test needs to be refactored to allow it to flow to completion so that it can be verified as expected.
[Fact]
public async Task GetAsync_Calls_IpSpecificGetterExtras() {
// Arrange
var mock = new Mock<IntegratedPlatformRepository> {
CallBase = true
};
mock.Protected()
.Setup<Task>("IpSpecificGetterExtras",
ItExpr.IsAny<OutputDTO>(), ItExpr.IsAny<CancellationToken>())
.Returns(Task.CompletedTask);
var subject = mock.Object;
// Act
await subject.GetAsync(Guid.NewGuid().ToString());
// Assert
mock
.Protected()
.Verify<Task>(
"IpSpecificGetterExtras", Times.Once(),
ItExpr.IsAny<OutputDTO>(), ItExpr.IsAny<CancellationToken>()
);
}
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);
I've been trying to write a few tests in NUnit for my generic methods, without success. I hope I can make my situation clear, since I had to heavily paraphrase my code.
DoBusinessLogic() is the method I want to test. It calls two other methods from the base class:
public class MySvc : BaseSvc, IMySvc
{
private readonly IMyRepo _repo;
private readonly IMyConnectorClass _connector
public MySvc(IMyRepo repo) : base(repo)
{
_repo = repo;
_connector = _repo.GetConnector();
}
public async Task DoBusinessLogic(int id1, int id2){
bool doesFirstObjectExist = await CheckMainObjExists<Foo>(id1, _connector);
await CheckSubObjExists<Bar>(id2, _connector);
// further business logic
}
}
Those methods, in turn, call the same repository method, but have different logic after it:
public abstract class BaseSvc : IBaseSvc
{
private readonly IBaseRepo _repo
protected BaseSvc(IBaseRepo repo)
{
_repo = repo;
}
protected async Task<bool> CheckMainObjExists<T>(int? id, IMyConnectorClass connector)
{
return await _repo.GetObjectByIdAsync<T>(Id, connector) != null;
}
protected async Task CheckSubObjExists<T>(int? id, IMyConnectorClass connector)
{
if (await _repo.GetObjectByIdAsync<T>(Id, connector) == null)
{ throw new Exception("Object not found!"); }
}
}
Next, I want to write unit a test for DoBusinessLogic() in the MySvc class. Unfortunately, it seems I can't mock the responses from the repository.
[TestFixture]
public class MySvcTests
{
private MySvc _svc;
private Mock<IMyRepo> _repoMock;
private Mock<IMyConnectorClass> _connectorMock;
[SetUp]
public void SetUp()
{
_repoMock = new Mock<IMyRepo>() {};
_connectorMock = new Mock<IMyConnectorClass>();
_repo.SetUp(r => r.GetConnector()).Return(_connectorMock.Object);
_svc = new MySvc(_repoMock);
}
/*
My intent in this test, is to make CheckMainObjExists() pass,
but make CheckSubObjExist() fail.
*/
[Test]
public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException()
{
// This should return an object
_repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync(new Foo());
// This should return null
_repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync((Bar) null);
Assert.Throws<Exception>(await _svc.DoBusinessLogic());
}
}
However, when I run the test, both methods that I set up for my mock repo return null, whereas I expect a "true" from the first.
I do not know where the problem is situated, but I have my suspicions:
Is it possible to setup a method, using a mocked object as a parameter? In this case, is it possible to use _connectorMock.Object as a setup parameter?
Is it possible to setup the same generic method multiple times, but for a different type each time? It's first setup for Foo, then for Bar.
I just tested this code and it runs as expected. Now I had to make a lot of assumptions just to get the code to compile and run which would mean that my test of your code is flawed as I may have fixed something that was omitted in your example.
I made no changes to your test setup code, which worked.
[TestClass]
public class MySvcTests {
[TestMethod]
[ExpectedException(typeof(Exception))]
public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException() {
var _repoMock = new Mock<IMyRepo>() { };
var _connectorMock = new Mock<IMyConnectorClass>();
_repoMock.Setup(r => r.GetConnector()).Returns(_connectorMock.Object);
var _svc = new MySvc(_repoMock.Object);
// This should return an object
_repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync(new Foo());
// This should return null
_repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync((Bar)null);
await _svc.DoBusinessLogic(0, 0);
}
}
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.