-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to test application service command handlers dealing with read models? #52
Comments
Our current basic test: final class LoginControllerTest extends AbstractControllerTest
{
/** @test */
public function itCanBeAccessedAnonymously(): void
{
$commandHandlerMock = $this->getMockBuilder(LoginHostHandler::class)
->disableOriginalConstructor()
->getMock();
$this->client->getContainer()->set(LoginHostHandler::class, $commandHandlerMock);
$content = [
'emailAddress' => '[email protected]',
'password' => 'test1234'
];
$this->client->request('POST', '/login', [], [], [], json_encode($content));
$this->assertTrue($this->client->getResponse()->isSuccessful());
}
} |
Seems that your example is not correct, IMO you can use your real |
Thank you! The |
I must confess I did not understand this line. Could you please explain? |
ah okay, thanks :-) Your subject under test is your $commandHandlerMock = $this->getMockBuilder(LoginHostHandler::class)
->disableOriginalConstructor()
->getMock(); Then you overwrite the $this->client->getContainer()->set(LoginHostHandler::class, $commandHandlerMock); then you call your Application (via http I guess) $this->client->request('POST', '/login', [], [], [], json_encode($content)); So the question is: why don't you just test the handler it self (as stated in the issue title)? You could do this, while abstracting the readModel away: function it_errors_on_unverified_host()
{
$alwaysUnverified = new class implements HostDetailsFinder {
public function isVerified() { return false; }
}
$subjectUnderTest = new LoginHostHandler(
$alwaysUnverified,
new UserPasswordEncoder
new JWTEncoder
);
$this->expectException(HostNotVerifiedException::class);
$subjectUnderTest(new LoginHost([
'emailAddress' => '[email protected]',
'password' => 'test1234'
]));
} What did we do:
If you want to functional test your app you can mock the readmodel away the same way, but for a functional test I'd prefer to produce the data you need and test your application end2end. Regarding your questions:
no would't.
only if i have to read state from the aggregate root without eventual concistency.
Im not 100% sure what you mean here, but when testing in isolation it's very easy to test multiple scenarios based on the ReadModel. Hope I understood your questions :-) |
Sorry for the late reply @HellPat !
I must confess the featured test file was confusing. The controller actually was only used to check if routes are correctly protected by the authentication. |
That's good news. $alwaysUnverified = new class implements HostDetailsFinder {
public function isVerified() { return false; }
} That is a very helpful part! I think that was the missing link. I will give it a try! Thank you very much. |
You're welcome. Let me know if you have further questions an I'll share my opinion |
BTW @HellPat Are you on Twitter? |
Yep, this is how I found your Issue :-) |
final class UniqueValueAddedTaxIdentifierNumberPolicy
{
private HostReadModelRepository $repository;
public function __construct(HostReadModelRepository $repository)
{
$this->repository = $repository;
}
public function isSatisfiedBy(HostId $hostId, ValueAddedTaxIdentificationNumber $vatId): bool
{
try {
$host = $this->repository->ofVatId($vatId);
} catch (HostNotFoundException $exception) {
return true;
}
if ($host->hostId()->sameValueAs($hostId)) {
return true;
}
return false;
}
} interface HostReadModelRepository
{
public function ofHostId(HostId $hostId): HostReadModel;
public function ofEmailAddress(EmailAddress $emailAddress): HostReadModel;
public function ofVerificationToken(string $token): HostReadModel;
public function ofVatId(ValueAddedTaxIdentificationNumber $vatId): HostReadModel;
} final class UniqueValueAddedTaxIdentifierNumberPolicyTest extends TestCase
{
/** @test */
public function itIsSatisfiedWhenVatIdIsUsedBySameHost(): void
{
$hostReadModelRepository = new class implements HostReadModelRepository {
public function ofHostId(HostId $hostId): HostReadModel
{}
public function ofEmailAddress(EmailAddress $emailAddress): HostReadModel
{}
public function ofVerificationToken(string $token): HostReadModel
{}
public function ofVatId(ValueAddedTaxIdentificationNumber $vatId): HostReadModel
{
return HostReadModel::fromArray([
'hostId' => 'b38f676e-221d-4807-97c9-8f549b13425e',
'emailAddress' => '[email protected]',
'verified' => true,
'profileCompleted' => true,
'activePlaces' => false,
'activePlan' => false,
]);
}
};
$policy = new UniqueValueAddedTaxIdentifierNumberPolicy($hostReadModelRepository);
$this->assertTrue($policy->isSatisfiedBy(
HostId::fromString('b38f676e-221d-4807-97c9-8f549b13425e'),
ValueAddedTaxIdentificationNumber::fromString('DE 123456789')
));
}
} Hi @HellPat , here is a different unit test that also involves repositories. public function ofHostId(HostId $hostId): HostReadModel; Can be written... new class implements HostReadModelRepository {
public function ofHostId(HostId $hostId): HostReadModel
{} ...without any return value at all. Though PHPStorm warns me about it: Should I change this and / or are there any more ways to shorten the code for the test? Thanks in advance. |
Yeah, In general I'd not mock the repository. e.g.
If you want to test e.g. final class UniqueValueAddedTaxIdentifierNumberPolicy
{
public function isSatisfiedBy(HostId $hostId, HostReadModel $host): bool
{
return $host->hostId()->sameValueAs($hostId);
}
} You can catch Your looks like final class UniqueValueAddedTaxIdentifierNumberPolicyTest extends TestCase
{
/** @test */
public function itIsSatisfiedWhenVatIdIsUsedBySameHost(): void
{
$hostReadModel = HostReadModel::fromArray([
'hostId' => 'b38f676e-221d-4807-97c9-8f549b13425e',
'emailAddress' => '[email protected]',
'verified' => true,
'profileCompleted' => true,
'activePlaces' => false,
'activePlan' => false,
]);
$policy = new UniqueValueAddedTaxIdentifierNumberPolicy();
$this->assertTrue($policy->isSatisfiedBy(
HostId::fromString('b38f676e-221d-4807-97c9-8f549b13425e'),
$hostReadModel
));
}
} but to be honest, you should already have a test for I'm not entirely sure what you want to test. |
The following implementation uses a CQRS Login Query through the service bus.
The authentication currently is based on Symfony Security and the
UserPasswordEncoderInterface
.How would you test this scenario?
First of all, would you move the logic away from the handler e.g. to a "LoginService"?
Would you even consider loading the WRITE model (event-sourced aggregate-root) though you don't want to change state?
Though you could add events e.g. "LoginFailed" to fill some audit log. But this actually is a query that should return a valid token.
Login Controller
Read Model
Command handler
The text was updated successfully, but these errors were encountered: