Comments (12)
You're welcome. Let me know if you have further questions an I'll share my opinion
from php-ddd.
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());
}
}
from php-ddd.
Seems that your example is not correct, HostReadModel
is never beeing used?
If you want to test "service command handlers dealing with read models" mocking them seems somehow wrong?
IMO you can use your real LoginHostHandler
and mock away every dependency of ``LoginHostHandler` which have side effects?
from php-ddd.
Seems that your example is not correct,
HostReadModel
is never beeing used?
Thank you! The $hostDetails
is the read model. I added a @var
doc to it.
from php-ddd.
IMO you can use your real
LoginHostHandler
and mock away every dependency of ``LoginHostHandler` which have side effects?
I must confess I did not understand this line. Could you please explain?
from php-ddd.
The $hostDetails is the read model. I added a @var doc to it.
ah okay, thanks :-)
Your subject under test is your LoginHostHandler
(as stated in the ticket title).
And in your example test you mock exactly this LoginHostHandler
$commandHandlerMock = $this->getMockBuilder(LoginHostHandler::class)
->disableOriginalConstructor()
->getMock();
Then you overwrite the LoginHostHandler
with your mock:
$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:
- we unit-tested
LoginHostHandler
in isolation, without framework - we mocked away the readmodel, since it's not "subject under test"
- since readmodel is mocked you can test multiple scenarios
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:
First of all, would you move the logic away from the handler e.g. to a "LoginService"?
no would't.
After all your Handler is already a single function service - reusable - totally fine.
Would you even consider loading the WRITE model (event-sourced aggregate-root) though you don't want to change state?
only if i have to read state from the aggregate root without eventual concistency.
Since you already have a ReadModel I'd use that.
Would you e.g. mock the repository or type-hint the interface and test different read model scenarios. Keeping the logic inside the command handler?
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 :-)
from php-ddd.
Sorry for the late reply @HellPat !
So the question is: why don't you just test the handler it self (as stated in the issue title)?
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.
The handlers aren't really part of this test. And mocking was the easiest way to get through to the thrown http status.
from php-ddd.
First of all, would you move the logic away from the handler e.g. to a "LoginService"?
no would't.
After all your Handler is already a single function service - reusable - totally fine.
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.
from php-ddd.
BTW @HellPat Are you on Twitter?
from php-ddd.
Yep, this is how I found your Issue :-)
from php-ddd.
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.
Following your example I recognized that anonymous classes do not have to implement the body or return value of the interface.
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.
from php-ddd.
Yeah,
If you have an Interface you MUST fulfill it. The more methods you have, the more complex it gets.
In general I'd not mock the repository.
The repository is interacting with your database, and if you test your Repository you should test it against the database.
e.g.
- perform some actions (commands)
- look if these actions lead to the desired result (read: the repository returns the right things)
If you want to test UniqueValueAddedTaxIdentifierNumberPolicy
you could pass the Readmodel directly.
e.g.
final class UniqueValueAddedTaxIdentifierNumberPolicy
{
public function isSatisfiedBy(HostId $hostId, HostReadModel $host): bool
{
return $host->hostId()->sameValueAs($hostId);
}
}
You can catch HostNotFoundException
at the place you use the Repository (where UniqueValueAddedTaxIdentifierNumberPolicy
is beeing used, or in a global ExceptionHandler)
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 sameValueAs
so the class UniqueValueAddedTaxIdentifierNumberPolicy
is not necessary at all.
I'm not entirely sure what you want to test.
from php-ddd.
Related Issues (20)
- Repositories inside or outside Domain Services HOT 1
- Event Enriching and external changes to read-model data
- When, where and how to create Summary Events HOT 3
- Passing read models (value objects representing state) / domain service to aggregate methods HOT 6
- Unit testing value objects with internal datetime calculation HOT 14
- Process Manager example with Symfony Messenger Command / Event Bus and ProophOS HOT 5
- Batch / Bulk operations handling multiple event-sourced aggregate roots HOT 3
- How to use factory methods on aggregates in CQRS - WRITE vs. READ model HOT 1
- How to keep read-models up-to-date when a name property was externally changed?
- How to upcast events with Prooph HOT 1
- Are CQRS commands part of the domain model? HOT 13
- Populate Projection with multiple tables HOT 2
- Where to call or pass a domain service? HOT 16
- How to implement the Equatable interface / Equals or SameValueAs method in value objects
- Domain Event Publisher for Doctrine Entities HOT 1
- Event Sourcing vs. Event-Driven Architecture (EDA)
- The repository pattern HOT 4
- Properties on Domain Events HOT 3
- PHP Command DTO with Symfony Constraints equivalent in Angular Forms HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from php-ddd.