英文:
Moq Verify says method was never called, but it was
问题
以下是代码部分的中文翻译:
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
};
}
}
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));
}
}
这些是您提供的代码的中文翻译部分。如果您需要更多帮助或其他问题的翻译,请随时告诉我。
英文:
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?
答案1
得分: 0
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.
英文:
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.
答案2
得分: 0
以下是您要翻译的代码部分:
查看更多细节。看起来 `BackgroundService.StartAsync` 将调用 `ExecuteAsync`,然后返回 `Task.CompletedTask`。
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// 存储我们正在执行的任务
_executingTask = ExecuteAsync(_stoppingCts.Token);
// 如果任务已经完成,则返回它,
// 这将传递取消和失败给调用者
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// 否则,它正在运行
return Task.CompletedTask;
}
这导致我的代码尚未完成执行,因此我的 moq 断言失败。我认为这两者之间的时间非常接近,因此在生成错误时,方法已被调用,这就是令人困惑的错误消息原因。
我通过简单地等待执行的任务完成来解决了这个问题:
await _kafkaConsumerService.StartAsync(CancellationToken.None);
await _kafkaConsumerService.ExecuteTask;
(请注意,上述代码是根据您提供的信息翻译的,其中包含了代码部分。)
英文:
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;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论