vkhorikov / CqrsInPractice

Source code for the CQRS in Practice Pluralsight course
https://enterprisecraftsmanship.com/ps-cqrs
MIT License
491 stars 171 forks source link

Testing Decorator Pattern #25

Open mohammadtaherri opened 7 months ago

mohammadtaherri commented 7 months ago

Hi

How should we test decorators? For example for testing the DatabaseRetryDecorator, we should mock ICommandHandler to throws an exception. Right? But based on your unit testing book and fragile tests, We can use mocks just for unmanaged dependencies. how to test Decorator pattern?

vkhorikov commented 7 months ago

Great question. This area is something where the recommendations from my book are lacking a bit. Ideally, you want to re-create the state under test in managed dependency itself (e.g the database), but for some test cases, it's difficult to do. For example, you can't easily put the DB into a state where it would reject requests so that you can test the retry logic. So instead, you have to mock it.

This is the only exception to the rule I described in the book -- i.e when the managed dependency is hard/impossible to bring to the desired state.

Note that you shouldn't mock ICommandHandler, but the database itself, to make sure the whole chain of middleware is working properly.

mohammadtaherri commented 4 months ago

Note that you shouldn't mock ICommandHandler, but the database itself, to make sure the whole chain of middleware is working properly.

Thanks Vladimir. But DatabaseRetryDecorator uses an ICommandHandler. how do i mock database? You mean I should test the DatabaseRetryDecorator with another commands?

for example should i create DatabaseRetryDecorator(DisenrollCommand(...)) and then pass the mocked database to DisenrollCommand?

But if I want to do this, then I have to write test for all commands, whenever I add a new decorator!!! And if I want to test decorators separately, then how do I pass them a mocked version of database?

another solution that occurs to me is to create a CustomCommand for tests and pass a database to it and then use it to test DatabaseRetryDecorator.

What do you mean by mocking database to test DatabaseRetryDecorator? And in general, what is the best way to test decorators?

vkhorikov commented 2 months ago

for example should i create DatabaseRetryDecorator(DisenrollCommand(...)) and then pass the mocked database to DisenrollCommand?

That's correct. You don't need to test all commands that are using that decorator, though, just one is enough.

An alternative here would be to just test the decorator manually. I know it doesn't sound right but sometimes writing a test just isn't worth it. An example could be an external dependency or a cross-cutting concern like this decorator. You can test it manually once, to make sure it works and then re-test it every time you make changes to it. You can also observe its behavior through logs whenever a database failure happens in dev/qa/prod environments.