The test program setup is composed of the following:

  • One IMediator field initialized with a Mediator instance, which enables all colleagues to interact with each other.
  • Two IChatRoom fields initialized with ChatRoom instances.
  • Three IParticipant uninitialized fields, later initialized with Participant instances.
  • Three TestMessageWriter instances, one per participant.
  • The constructor registers all handlers with the Mediator instance so it knows how to handle commands and queries. It also creates the participants.

Once again, the names of the participants are randomly generated.

The TestMessageWriter implementation is a little different and accumulates the data in a list of tuples (List<(IChatRoom, ChatMessage)>) to assess what the participants send:

private class TestMessageWriter : IMessageWriter
{
    public List<(IChatRoom chatRoom, ChatMessage message)> Output { get; } = new();
    public void Write(IChatRoom chatRoom, ChatMessage message)
    {
        Output.Add((chatRoom, message));
    }
}

Here is the first test case:

[Fact]
public void A_participant_should_be_able_to_list_the_participants_that_joined_a_chatroom()
{
    _reagen.Join(_room1);
    _reagen.Join(_room2);
    _garner.Join(_room1);
    _cornelia.Join(_room2);
    var room1Participants = _reagen.ListParticipantsOf(_room1);
    Assert.Collection(room1Participants,
        p => Assert.Same(_reagen, p),
        p => Assert.Same(_garner, p)
    );
}

In the preceding test case, Reagen and Garner join Room 1, while Reagen and Cornelia join Room 2. Then Reagen requests the list of participants from Room 1, which outputs Reagen and Garner. Under the hood, it uses commands and queries through a mediator, breaking tight coupling between the colleagues. Here is a sequence diagram representing what happens when a participant joins a chatroom:

 Figure 16.10: Sequence diagram representing the flow of a participant (p) joining a chatroom (c)Figure 16.10: Sequence diagram representing the flow of a participant (p) joining a chatroom (c) 

  1. The participant (p) creates a JoinChatRoom command (joinCmd).
  2. p sends joinCmd through the mediator (m).
  3. m finds and dispatches joinCmd to its handler (handler).
  4. handler executes the logic (adds p to the chatroom).
  5. joinCmd ceases to exist afterward; commands are ephemeral.

That means Participant never interacts directly with ChatRoom or other participants.Then a similar workflow happens when a participant requests the list of participants of a chatroom:

 Figure 16.11: Sequence diagram representing the flow of a participant (p) requesting the list of participants of a chatroom (c)Figure 16.11: Sequence diagram representing the flow of a participant (p) requesting the list of participants of a chatroom (c) 

  1. Participant (p) creates a ListParticipants query (listQuery).
  2. p sends listQuery through the mediator (m).
  3. m finds and dispatches the query to its handler (handler).
  4. handler executes the logic (lists the participants of the chatroom).
  5. listQuery ceases to exist afterward; queries are also ephemeral.

Once again, Participant does not interact directly with ChatRoom.Here is another test case where Participant sends a message to a chatroom, and another Participant receives it:

[Fact]
public void A_participant_should_receive_new_messages()
{
    _reagen.Join(_room1);
    _garner.Join(_room1);
    _garner.Join(_room2);
    _reagen.SendMessageTo(_room1, “Hello!”);
    Assert.Collection(_garnerMessageWriter.Output,
    line =>
    {
        Assert.Equal(_room1, line.chatRoom);
        Assert.Equal(_reagen, line.message.Sender);
        Assert.Equal(“Hello!”, line.message.Message);
    }
  );
}

In the preceding test case, Reagen joins Room 1 while Garner joins Rooms 1 and 2. Then Reagen sends a message to Room 1, and we verify that Garner received it once.The SendMessageTo workflow is very similar to the other one that we saw but with a more complex command handler:

 Figure 16.12: Sequence diagram representing the flow of a participant (p) sending a message (msg)to a chatroom (c)Figure 16.12: Sequence diagram representing the flow of a participant (p) sending a message (msg)to a chatroom (c) 

From that diagram, we can observe that we pushed the logic to the Handler class of the SendChatMessage feature. All the other actors work together with limited to no knowledge of each other.This demonstrates how CQS works with a mediator:

  1. A consumer (the participant in this case) creates a command (or a query).
  2. The consumer sends that command through the mediator.
  3. The mediator sends that command to one or more handlers, each executing their piece of logic for that command.

You can explore the other test cases to familiarize yourself with the program and the concepts.

You can debug the tests in Visual Studio; use breakpoints combined with Step Into (F11) and Step Over (F10) to explore the sample.

I also created a ChatModerator instance that sends a message in a “moderator chatroom” when a message contains a word from the badWords collection. That test case executes multiple handlers for each SendChatMessage.Command. I’ll leave you to explore these other test cases yourself so we don’t wander astray from our goal.