Search code examples
integration-testingtddbddcqrsmediatr

does returning object in insert method violate cqrs pattern?


I have implemented MediatR in my asp.net core web api application.

Our controllers simply send a command or query to MediatR and return the result.

[HttpPost]
public async Task<IActionResult> CreateQuestion(CreateQuestionCommand command)
{
    await Mediator.Send(command);
    return Ok();
}

Due to the CQRS pattern that says commands should not return any value we don't return any value in our MediatR commands.

everything was normal since we decided to write some BDD tests.

In our BDD tests there is a simple scenario like this:

Scenario: [Try to add a new Question]
Given [I'm an authorized administrator]
When [I create a new Question with Title '<Title>' and '<Description>' and '<IsActive>' and '<IndicatorId'>]
Then [A question with Title '<Title>' and '<Description>' and '<IsActive>' and '<IndicatorId'> should be persisted in database]
Examples: 
| Title                | Description                | IsActive | IndicatorId                          |
| This is a test title | this is a test description | true     | 3cb23a10-107a-4834-8c1a-3fd893217861 |

We set Id property of Question object in its constructor. which means we don't know what Id we have set to this newly created Question and therefore we can't read it after adding it to database in a test environment.

my question was how to test it in BDD? this is my test step implementation:

[When(@"\[I create a new Question with Title '([^']*)' and '([^']*)' and '([^']*)' and '([^']*)'>]")]
public async void WhenICreateANewQuestionWithTitleAndAndAnd(string title, string description, bool isActive, Guid indicatorId)
{
    var command = new CreateQuestionCommand
    {
        Title = title,
        Description = description,
        IndicatorId = indicatorId
    };
    await questionController.CreateQuestion(command);

}

[Then(@"\[A question with Title '([^']*)' and '([^']*)' and '([^']*)' and '([^']*)'> should be persisted in database]")]
public void ThenAQuestionWithTitleAndAndAndShouldBePersistedInDatabase(string title, string description, string isActive, string indicatorId)
{
   //? how to retrieve the created data. We don't have the Id here
}

How can I retrieve the added Question?

Should I change my command handlers to return my object after inserting it to database?

And if I do so, wouldn't it be a CQRS violation?

Thank you for your time.


Solution

  • There's a couple of ways to go about this, but it depends on how you actually expect the system to behave. If you're doing BDD you should focus on the kind of behaviour that would be observable by real users.

    If you're implementing a system that allows users to create (and save, I infer) questions in a (Q&A?) database, then should they not have the ability to view or perhaps edit the question afterwards?

    As the OP is currently phrased, the trouble writing the test seems to imply a lacking capability of the system in general. If the test can't identify the entity that was created, then how can a user?

    The CreateQuestion method only returns Ok(), which means that there's no data in the response. Thus, as currently implemented, there's no way for the sender of the POST request to subsequently navigate to or retrieve the new resource.

    How are users supposed to interact with the service? This should give you a hint about how to write the test.

    A common REST pattern is to return the address of the new resource in the response's Location header. If the CreateQuestion method were to do that, the test would also be able to investigate that value and perhaps act on it.

    Another option is to return the entity ID in the response body. Again, if that's the way things are supposed to work, the test could verify that.

    Other systems are more asynchronous. Perhaps you only put the CreateQuestionCommand on a queue, to be handled later. In such a case I'd write a test that verifies that the command was added to the queue. You could also write a more long-running test that then waits for an asynchronous message handler to process the command, but if you want to test something like, you also need to deal with timeouts.

    In a comment, you write:

    I think he might add the question and then read all questions from database and see if his question is amongst them. He'd probably test without knowing Id.

    Do you expect real users of the system to spelunk around in your database? Unless you do, the database is not part of a system's observable behaviour - it's an implementation detail. Thus, behaviour-driven design shouldn't concern itself with the contents of databases, but how a system behaves.

    In short, find out how to observe the behaviour you want the system to have, and then test that.