The current project I’m doing is a CQRS based project, using the ncqrs framework. Today, I came to a phase where I needed some mocking done for some of my domain services. I chose FakeItEasy to handle this for me. So I started by just adding a reference to my DomainScenario
project using a Nuget package.
For some of my complex scenarios, I ran into some issues which took me about 45 minutes to fix one of my tests. For fixing all other failing tests, I had to do the same trick.
Before I explain the issue I ran into with FakeItEasy, I have to mention I use my aggregate roots to prepare my eventstore. My issue is related to this specific way of preparing the eventstore.
For example, I have a Participant
aggregate root like below:
public class Participant : BisAggregateRoot
{
private readonly IUniqueParticipantNrGuard _uniqueFormattedParticipantNrVerifier =
NcqrsEnvironment.Get<IUniqueParticipantNrGuard>();
private string _participantNrFormat;
private int _participantNr;
private Gender _gender;
private PartialDate _birthDate;
private string _remark;
public Guid ParticipantId { get { return EventSourceId; } }
private Participant()
{
}
public Participant(Guid participantId, ScientificStudy scientificStudy,
string participantNrFormat,
int participantNr, Gender gender, PartialDate birthdate)
: base(participantId)
{
_uniqueFormattedParticipantNrVerifier.GuardIsUnique
(scientificStudy.ScientificStudyId, participantId,
string.Format(participantNrFormat, participantNr));
ApplyEvent(new ParticipantAddedEvent(Credentials)
{
ParticipantId = participantId,
ParticipantNrFormat = participantNrFormat,
ParticipantNr = participantNr,
ScientificStudyId = scientificStudy.ScientificStudyId,
Gender = gender.ActualValue,
Birthdate = birthdate.ActualValue
});
}
public void Edit(ScientificStudy scientificStudy, string participantNrFormat,
int participantNr, Gender gender, PartialDate birthdate, string remark)
{
_uniqueFormattedParticipantNrVerifier.GuardIsUnique
(scientificStudy.ScientificStudyId, ParticipantId,
string.Format(participantNrFormat, participantNr));
ApplyEvent(new ParticipantEditedEvent(Credentials)
{
ParticipantId = ParticipantId,
ParticipantNrFormat = participantNrFormat,
ParticipantNr = participantNr,
Gender = gender.ActualValue,
Birthdate = birthdate.ActualValue
});
}
protected void OnParticipantAdded(ParticipantAddedEvent e)
{
_claimedAliquots = new List<ClaimedAliquot>();
_participantNrFormat = e.ParticipantNrFormat;
_participantNr = e.ParticipantNr;
_gender = new Gender(e.Gender);
_birthDate = new PartialDate(e.Birthdate);
}
protected void OnParticipantEdited(ParticipantEditedEvent e)
{
_participantNrFormat = e.ParticipantNrFormat;
_participantNr = e.ParticipantNr;
_gender = new Gender(e.Gender);
_birthDate = new PartialDate(e.Birthdate);
_remark = e.Remark;
}
}
As you can see, both the edit and the construction of this aggregate use the same domain service to validate if the participant number is unique. Keep this in mind because of my eventstore
preparation style, because this relates to the problem.
For my eventstore
, I use some fluent builder thing I built, which uses the domainobjects
to generate the actual events.
public static class New
{
public static ScientificStudy ScientificStudy(string nr = "M001",
string name = "Maastricht Study",
string participantNrFormat = "P{0}", string boxNameFormat = "B{0}")
{
return new ScientificStudy(Default.Id.For.ScientificStudy, nr,
name, participantNrFormat, boxNameFormat);
}
public static Participant Participant(ScientificStudy scientificStudy,
string participantNrFormat = "P{0}", int participantNr = 1,
int gender = 0, string birthdate = "19861117")
{
var realGender = new Gender(gender);
var realBirthDate = new PartialDate(birthdate);
return new Participant(Default.Id.For.Participant, scientificStudy,
participantNrFormat, participantNr, realGender, realBirthDate);
}
}
This builder is used like below in my tests. Notice the GetChanges
method is an extension method on my aggregateRoots
.
protected override void SetupDependencies()
{
base.SetupDependencies();
var eventStore = NcqrsEnvironment.Get<IEventStore>();
var scientificStudy = New.ScientificStudy();
var scientificStudyEvent = scientificStudy.TrackChanges().GetChanges();
var participantEvents = New.Participant(scientificStudy).GetChanges();
eventStore.Store(Prepare.Events(scientificStudyEvent).ForSourceUncomitted(
Default.Id.For.ScientificStudy, Default.Id.Random()));
eventStore.Store(Prepare.Events(participantEvents).ForSourceUncomitted(
Default.Id.For.Participant, Default.Id.Random()));
}
For the test I want to do, I registered my Fakes in first instance like below, which made my test fail. I am trying to test if the editing causes an ‘FormattedParticipantNrShouldBeUniqueException
’ when changing the participantNr
into an already existing participantNr
.
protected override void RegisterFakesInConfiguration(EnvironmentConfigurationWrapper configuration)
{
var commandService = new CommandService();
commandService.RegisterExecutor(new EditParticipantCommandExecutor());
var uniqueParticipantGuard = A.Fake<IUniqueParticipantNrGuard>();
A.CallTo(uniqueParticipantGuard).DoesNothing().Once();
A.CallTo(uniqueParticipantGuard).Throws(
new FormattedParticipantNrShouldBeUniqueException(_formattedParticipantNr));
configuration.Register<ICommandService>(commandService);
configuration.Register(uniqueParticipantGuard);
base.RegisterFakesInConfiguration(configuration);
}
After 45 minutes searching and discussing with my colleague, we found a solution.
protected override void RegisterFakesInConfiguration
(EnvironmentConfigurationWrapper configuration)
{
var commandService = new CommandService();
commandService.RegisterExecutor(new EditParticipantCommandExecutor());
var uniqueParticipantGuard = A.Fake<IUniqueParticipantNrGuard>();
A.CallTo(uniqueParticipantGuard).Throws(
new FormattedParticipantNrShouldBeUniqueException(_formattedParticipantNr));
A.CallTo(uniqueParticipantGuard).DoesNothing().Once();
configuration.Register<ICommandService>(commandService);
configuration.Register(uniqueParticipantGuard);
base.RegisterFakesInConfiguration(configuration);
}
As you can see, the solution was really simple, but hard to find, because debugging didn’t highlight this was the issue.
Just switch the order of configuring the Mocked/Faked object.
Last but not least, I show you my actual test method.
[Then]
public void it_should_throw_an_formatted_participant_nr_
should_be_unique_exception_containing_the_formatted_participant_nr()
{
var exception = (FormattedParticipantNrShouldBeUniqueException)CaughtException;
exception.FormattedParticipantNr.Should().Be(_formattedParticipantNr);
}
I’m not sure if I was just doing it wrong or if FakeItEasy just needs a fix to prevent you from running into this issue. Hope I made the issue clear and it will save you some time when encountering the same issues.