Moq Verify 表示方法从未被调用,但实际上它已经被调用了。

huangapple go评论91阅读模式
英文:

Moq Verify says method was never called, but it was

问题

以下是代码部分的中文翻译:

  1. public class KafkaConsumerService : BackgroundService
  2. {
  3. private readonly IConsumer<string, Transaction> _kafkaConsumer;
  4. private readonly IRepository _repository;
  5. private readonly ICalculator _calculator;
  6. public KafkaConsumerService(
  7. IConsumer<string, Transaction> kafkaConsumer,
  8. IRepository repository,
  9. ICalculator calculator
  10. )
  11. {
  12. _kafkaConsumer = kafkaConsumer;
  13. _repository = repository;
  14. _calculator = calculator;
  15. }
  16. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  17. {
  18. var consumeResult = await Task.Run(() => _kafkaConsumer.Consume(stoppingToken), stoppingToken);
  19. var transaction = consumeResult.Message.Value;
  20. var account = await _repository.GetAccount(transaction.Account);
  21. await _repository.UpdateAccount(_calculator.CalculateAccount(account, Normalize(transaction)));
  22. }
  23. private Transaction Normalize(Transaction transaction)
  24. {
  25. if (!transaction.IsCancellation)
  26. {
  27. return transaction;
  28. }
  29. return new Transaction(transaction)
  30. {
  31. Amount = transaction.Amount * -1,
  32. IsCancellation = false
  33. };
  34. }
  35. }
  1. public class KafkaConsumerServiceTest
  2. {
  3. private readonly Mock<IConsumer<string, Transaction>> _kafka = new();
  4. private readonly Mock<IRepository> _repository = new();
  5. private readonly Mock<ICalculator> _calculator = new();
  6. private readonly Fixture _fixture = new();
  7. private readonly KafkaConsumerService _kafkaConsumerService;
  8. public KafkaConsumerServiceTest()
  9. {
  10. _kafkaConsumerService = new KafkaConsumerService(_kafka.Object, _repository.Object, _calculator.Object);
  11. }
  12. [Fact]
  13. public async Task KafkaConsumerService_ProcessesCancelationTransaction()
  14. {
  15. _fixture.Customize<Transaction>(composer => composer
  16. .With(transaction => transaction.IsCancellation, true)
  17. );
  18. var transaction = _fixture.Create<Transaction>();
  19. _kafka
  20. .Setup(consumer => consumer.Consume(It.IsAny<CancellationToken>()))
  21. .Returns(new ConsumeResult<string, Transaction>
  22. {
  23. Message = new Message<string, Transaction>
  24. {
  25. Value = transaction,
  26. },
  27. });
  28. var result = _fixture.Create<Account>() with
  29. {
  30. AccountName = transaction.Account
  31. };
  32. _repository
  33. .Setup(repository => repository.GetAccount(transaction.Account))
  34. .ReturnsAsync(result);
  35. _calculator
  36. .Setup(calculator => calculator.CalculateAccount(It.IsAny<Account?>(), It.IsAny<Transaction>()))
  37. .Returns(result);
  38. await _kafkaConsumerService.StartAsync(CancellationToken.None);
  39. _repository.Verify(repository =>
  40. repository.GetAccount(transaction.Account)
  41. );
  42. _calculator.Verify(calculator =>
  43. calculator.CalculateAccount(result, transaction)
  44. );
  45. _repository.Verify(repository => repository.UpdateAccount(result));
  46. }
  47. }

这些是您提供的代码的中文翻译部分。如果您需要更多帮助或其他问题的翻译,请随时告诉我。

英文:

I have the following code which reads a Transaction from Kafka, and updates the account balance to show that transaction

  1. public class KafkaConsumerService : BackgroundService
  2. {
  3. private readonly IConsumer&lt;string, Transaction&gt; _kafkaConsumer;
  4. private readonly IRepository _repository;
  5. private readonly ICalculator _calculator;
  6. public KafkaConsumerService(
  7. IConsumer&lt;string, Transaction&gt; kafkaConsumer,
  8. IRepository repository,
  9. ICalculator calculator
  10. )
  11. {
  12. _kafkaConsumer = kafkaConsumer;
  13. _repository = repository;
  14. _calculator = calculator;
  15. }
  16. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  17. {
  18. var consumeResult = await Task.Run(() =&gt; _kafkaConsumer.Consume(stoppingToken), stoppingToken);
  19. var transaction = consumeResult.Message.Value;
  20. var account = await _repository.GetAccount(transaction.Account);
  21. await _repository.UpdateAccount(_calculator.CalculateAccount(account, Normalize(transaction)));
  22. }
  23. private Transaction Normalize(Transaction transaction)
  24. {
  25. if (!transaction.IsCancellation)
  26. {
  27. return transaction;
  28. }
  29. return new Transaction(transaction)
  30. {
  31. Amount = transaction.Amount * -1,
  32. IsCancellation = false
  33. };
  34. }
  35. }

I have then written the following unit test for this, using XUnit and Moq

  1. public class KafkaConsumerServiceTest
  2. {
  3. private readonly Mock&lt;IConsumer&lt;string, Transaction&gt;&gt; _kafka = new();
  4. private readonly Mock&lt;IRepository&gt; _repository = new();
  5. private readonly Mock&lt;ICalculator&gt; _calculator = new();
  6. private readonly Fixture _fixture = new();
  7. private readonly KafkaConsumerService _kafkaConsumerService;
  8. public KafkaConsumerServiceTest()
  9. {
  10. _kafkaConsumerService = new KafkaConsumerService(_kafka.Object, _repository.Object, _calculator.Object);
  11. }
  12. [Fact]
  13. public async Task KafkaConsumerService_ProcessesCancelationTransaction()
  14. {
  15. _fixture.Customize&lt;Transaction&gt;(composer =&gt; composer
  16. .With(transaction =&gt; transaction.IsCancellation, true)
  17. );
  18. var transaction = _fixture.Create&lt;Transaction&gt;();
  19. _kafka
  20. .Setup(consumer =&gt; consumer.Consume(It.IsAny&lt;CancellationToken&gt;()))
  21. .Returns(new ConsumeResult&lt;string, Transaction&gt;
  22. {
  23. Message = new Message&lt;string, Transaction&gt;
  24. {
  25. Value = transaction,
  26. },
  27. });
  28. var result = _fixture.Create&lt;Account&gt;() with
  29. {
  30. AccountName = transaction.Account
  31. };
  32. _repository
  33. .Setup(repository =&gt; repository.GetAccount(transaction.Account))
  34. .ReturnsAsync(result);
  35. _calculator
  36. .Setup(calculator =&gt; calculator.CalculateAccount(It.IsAny&lt;Account?&gt;(), It.IsAny&lt;Transaction&gt;()))
  37. .Returns(result);
  38. await _kafkaConsumerService.StartAsync(CancellationToken.None);
  39. _repository.Verify(repository =&gt;
  40. repository.GetAccount(transaction.Account)
  41. );
  42. _calculator.Verify(calculator =&gt;
  43. calculator.CalculateAccount(result, transaction)
  44. );
  45. _repository.Verify(repository =&gt; repository.UpdateAccount(result));
  46. }
  47. }

However I then get the following error

  1. Moq.MockException
  2. Expected invocation on the mock at least once, but was never performed: repository =&gt; repository.GetAccount(&quot;Account73ccea18-e39c-493f-9533-7af7f983b8ab&quot;)
  3. Performed invocations:
  4. Mock&lt;IRepository:1&gt; (repository):
  5. IRepository.GetAccount(&quot;Account73ccea18-e39c-493f-9533-7af7f983b8ab&quot;)
  6. IRepository.UpdateAccount(Account { AccountName = Account73ccea18-e39c-493f-9533-7af7f983b8ab, Amount = 119 })

As you can see it says the method GetAccount(&quot;Account73ccea18-e39c-493f-9533-7af7f983b8ab&quot;) 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:

  1. return new Transaction(transaction)
  2. {
  3. Amount = transaction.Amount * -1,
  4. IsCancellation = false
  5. };

When you verify against an instance, it is doing a reference check so it cannot be a different newly made object.

Try

  1. _repository.Verify(repository =>
  2. repository.GetAccount(It.IsAny<string>())
  3. );
  4. _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:

  1. return new Transaction(transaction)
  2. {
  3. Amount = transaction.Amount * -1,
  4. IsCancellation = false
  5. };

When you verify against an instance, it is doing a reference check so it cannot be a different newly made object.

Try

  1. _repository.Verify(repository =&gt;
  2. repository.GetAccount(It.IsAny&lt;string&gt;())
  3. );
  4. _repository.Verify(repository =&gt; repository.UpdateAccount(It.IsAny&lt;Transaction&gt;()));

You may also use It.Is&lt;Transaction&gt;(t =&gt; t.AccountName == &quot;account&quot;) to validate specific values in the assertion.

答案2

得分: 0

以下是您要翻译的代码部分:

  1. 查看更多细节。看起来 `BackgroundService.StartAsync` 将调用 `ExecuteAsync`,然后返回 `Task.CompletedTask`
  2. public virtual Task StartAsync(CancellationToken cancellationToken)
  3. {
  4. // 存储我们正在执行的任务
  5. _executingTask = ExecuteAsync(_stoppingCts.Token);
  6. // 如果任务已经完成,则返回它,
  7. // 这将传递取消和失败给调用者
  8. if (_executingTask.IsCompleted)
  9. {
  10. return _executingTask;
  11. }
  12. // 否则,它正在运行
  13. return Task.CompletedTask;
  14. }

这导致我的代码尚未完成执行,因此我的 moq 断言失败。我认为这两者之间的时间非常接近,因此在生成错误时,方法已被调用,这就是令人困惑的错误消息原因。

我通过简单地等待执行的任务完成来解决了这个问题:

  1. await _kafkaConsumerService.StartAsync(CancellationToken.None);
  2. await _kafkaConsumerService.ExecuteTask;

(请注意,上述代码是根据您提供的信息翻译的,其中包含了代码部分。)

英文:

Looking into this further. It appears that BackgroundService.StartAsync will call ExecuteAsync, and then return Task.CompletedTask

  1. public virtual Task StartAsync(CancellationToken cancellationToken)
  2. {
  3. // Store the task we&#39;re executing
  4. _executingTask = ExecuteAsync(_stoppingCts.Token);
  5. // If the task is completed then return it,
  6. // this will bubble cancellation and failure to the caller
  7. if (_executingTask.IsCompleted)
  8. {
  9. return _executingTask;
  10. }
  11. // Otherwise it&#39;s running
  12. return Task.CompletedTask;
  13. }

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

  1. await _kafkaConsumerService.StartAsync(CancellationToken.None);
  2. await _kafkaConsumerService.ExecuteTask;

huangapple
  • 本文由 发表于 2023年2月19日 00:07:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75494603.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定