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;
Related
I am having the following method which calls an insert and update methods.
Conditions
In the below ProcessBspLoan function, I am calling other function named getBspData
if getBspData returns any result. I will insert the result
and update the database as a success for a particular Id
if getBspData throws any Exception I will only update the database as a fail for a particular Id without inserting
Here is the ProcessBspLoan function in the given class
public class BspLoanProcessor : IBspLoanProcessor
{
private readonly IBspClient _bspService;
private readonly IBspRepository _bspRepository;
private readonly ILogger<BspLoanProcessor> _logger;
private readonly IMapper _mapper;
private readonly IFhaRepository _fhaRepository;
public BspLoanProcessor(IBspClient bspService, ILogger<BspLoanProcessor> logger,
IMapper mapper, IBspRepository bSPRepository, IFhaRepository fhaRepository)
{
_bspService = bspService;
_logger = logger;
_mapper = mapper;
_bspRepository = bSPRepository;
_fhaRepository = fhaRepository;
}
public async Task<bool> ProcessBspLoan(BspLoanDetails bspLoanDetails, string transactionId, string triggerType)
{
FhaTransactionDetails fhaData = _mapper.Map<FhaTransactionDetails>(bspLoanDetails);
try
{
////////////////////////////Calling getBspData///////////////////////////////////////
var result = await _bspService.getBspData(bspLoanDetails.NsmLoanNumber, transactionId);
result.TransactionId = transactionId;
await _bspRepository.InsertBspResponse(result);
await _fhaRepository.UpdateFhaTransactionWithLoanNumber(transactionId,"SUCCESS");
return true;
}
catch(Exception ex)
{
await _fhaRepository.UpdateFhaTransactionWithLoanNumber(transactionId,"FAIL");
return false;
}
}
}
I wrote the test method for the above function want to check it is returning true or false based on inputs provided
Here is my test function
public async Task Test1Async()
{
Mock<IBspLoanProcessor> _bspLoanProcessor = new Mock<IBspLoanProcessor>();
Mock<IBspRepository> _bspRepository = new Mock<IBspRepository>();
Mock<IFhaRepository> _fhaRepository = new Mock<IFhaRepository>();
Mock<IBspClient> _bspClient = new Mock<IBspClient>();
BspLoanDetails bspLoanDetails = new BspLoanDetails
{
TriggerType = "BLOB",
Attempts = 1,
FirstRunDateTime = DateTime.Now.ToUniversalTime()
};
----> 1
_bspClient.Setup(x => x.getBspData(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(new BspResponseDetails()));
----> 2
_bspRepository.Setup(x => x.InsertBspResponse(It.IsAny<BspResponseDetails>())).Returns(Task.CompletedTask);
----> 3
_fhaRepository.Setup(x => x.UpdateFhaTransactionWithLoanNumber(It.IsAny<string>(),It.IsAny<string>())).Returns(Task.CompletedTask);
bool value = await _bspLoanProcessor.Object.ProcessBspLoan(bspLoanDetails, "123", "SCHEDULE");
Assert.True(value);
}
In the above the test, I am checking the first condition
I am returning data as an object whenever anyone called gets data
Inserting BspResponsemethod also I am returning task.completedTask
UpdateFhaTransactionWithLoanNumber also I am returning task.completedTask
The actual expected output is true, but it is returning false.
I am new to Moq and xUnit. Please help me to resolve this issue and also test async and await methods.
IBspClient interface
public interface IBspClient
{
Task<BspResponseDetails> getBspData(string loanNumber,string tid);
Task<BspHealthCheck> GetHealthStatus();
}
IBspRepository Interface
public interface IBspRepository
{
Task InsertBspResponse(BspResponseDetails bsp);
}
IFhaRepository interface
public interface IFhaRepository
{
Task UpdateFhaTransactionWithLoanNumber(string tid, string status);
}
Thanks in advance
bool value = await _bspLoanProcessor.Object
You're calling a method on your mock. You shouldn't mock the system-under-test, you only mock its dependencies.
Just new the class you want to test, otherwise you will be testing that your mock framework does what you set it up to.
And because you didn't setup ProcessBspLoan(), it returns the default for its return value, so false.
I have to write unit test to verify the code which was run by Task.Run(),which is wrapped inside an async action as shown below.Since I am not awaiting for the task to complete I am not able to achieve this as Task.Run() runs separately.
The requirement is that The call this.KeepAlive(accountNumber, token) should not be waited to complete to call the next statement.But if that KeepAlive service call fails or any validation fails then it should be logged.
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
readonly IService service;
readonly IService2 service2;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IService service, IService2 service2)
{
_logger = logger;
this.service = service;
this.service2 = service2;
}
[HttpGet]
public async Task<bool> Testmethod(string accountNumber, string viewName, CancellationToken token)
{
_ = Task.Run(() => this.KeepAlive(accountNumber, token));
var accountStatus = await this.service2.GetValidName(viewName, token);
return accountStatus=="Myname";
}
private async Task KeepAlive(string name, CancellationToken token)
{
if (string.IsNullOrEmpty(name))
{
_logger.LogError("name is empty or null");
return;
}
try
{
var isAlive = await this.service.ChekStatusAsyc(name, token);
if (!isAlive)
{
_logger.LogError("Unable to process the request");
}
}
catch
{
_logger.LogError("Service ChekStatusAsyc Failed");
}
}
}
}
I need to verify below in my unit tests
whether service was called
validation logging happened
Exception logging
The Test which was written as below will not work, since I am not awaiting for task to complete.
[Fact]
public async void KeepAliveAsyncShouldBeCalledErrorShouldBeLoggedIfServiceFails()
{
var mockLogger = new Mock<ILogger<WeatherForecastController>>();
var mockservice = new Mock<service>();
mockservice.Setup(x => x.ChekStatusAsyc(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(false);
var mockservice2 = new Mock<service2>();
var controller = new WeatherForecastController(mockLogger.Object, mockservice.Object, mockservice2.Object);
var result = await controller.Testmethod("account0", "test");
mockservice.Verify(x => x.ChekStatusAsyc(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
}
As I am not awaiting, I can't verify anything which was run by Task.Run().
If I use
await this.KeepAlive(accountNumber, token) then the test case which I wrote will work as expected,but it will wait for the task to complete.which is not as per the requirement.
Any suggestions?
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));
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.