Skip to content

Commit 6ee6454

Browse files
committed
add example for observable behavior vs implementation details
1 parent 2a2681e commit 6ee6454

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

README.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,8 +733,206 @@ final class ValidUnitExampleTest extends TestCase
733733

734734
## Observable behaviour vs implementation details
735735

736+
:x: Bad:
737+
738+
```php
739+
final class ApplicationService
740+
{
741+
public function __construct(private SubscriptionRepositoryInterface $subscriptionRepository) {}
742+
743+
public function renewSubscription(int $subscriptionId): bool
744+
{
745+
$subscription = $this->subscriptionRepository->findById($subscriptionId);
746+
747+
if (!$subscription->getStatus()->isEqual(Status::expired())) {
748+
return false;
749+
}
750+
751+
$subscription->setStatus(Status::active());
752+
$subscription->setModifiedAt(new \DateTimeImmutable());
753+
return true;
754+
}
755+
}
756+
```
757+
758+
```php
759+
final class Subscription
760+
{
761+
private Status $status;
762+
763+
private \DateTimeImmutable $modifiedAt;
764+
765+
public function __construct(Status $status, \DateTimeImmutable $modifiedAt)
766+
{
767+
$this->status = $status;
768+
$this->modifiedAt = $modifiedAt;
769+
}
770+
771+
public function getStatus(): Status
772+
{
773+
return $this->status;
774+
}
775+
776+
public function setStatus(Status $status): void
777+
{
778+
$this->status = $status;
779+
}
780+
781+
public function getModifiedAt(): \DateTimeImmutable
782+
{
783+
return $this->modifiedAt;
784+
}
785+
786+
public function setModifiedAt(\DateTimeImmutable $modifiedAt): void
787+
{
788+
$this->modifiedAt = $modifiedAt;
789+
}
790+
}
791+
```
792+
793+
```php
794+
final class InvalidTestExample extends TestCase
795+
{
796+
/**
797+
* @test
798+
*/
799+
public function renew_an_expired_subscription_is_possible(): void
800+
{
801+
$modifiedAt = new \DateTimeImmutable();
802+
$expiredSubscription = new Subscription(Status::expired(), $modifiedAt);
803+
$repository = $this->createStub(SubscriptionRepositoryInterface::class);
804+
$repository->method('findById')->willReturn($expiredSubscription);
805+
$sut = new ApplicationService($repository);
806+
807+
$result = $sut->renewSubscription(1);
808+
809+
self::assertEquals(Status::active(), $expiredSubscription->getStatus());
810+
self::assertGreaterThan($modifiedAt, $expiredSubscription->getModifiedAt());
811+
self::assertTrue($result);
812+
}
813+
814+
/**
815+
* @test
816+
*/
817+
public function renew_an_active_subscription_is_not_possible(): void
818+
{
819+
$modifiedAt = new \DateTimeImmutable();
820+
$activeSubscription = new Subscription(Status::active(), $modifiedAt);
821+
$repository = $this->createStub(SubscriptionRepositoryInterface::class);
822+
$repository->method('findById')->willReturn($activeSubscription);
823+
$sut = new ApplicationService($repository);
824+
825+
$result = $sut->renewSubscription(1);
826+
827+
self::assertEquals($modifiedAt, $activeSubscription->getModifiedAt());
828+
self::assertFalse($result);
829+
}
830+
}
831+
```
832+
833+
:heavy_check_mark: Good:
834+
835+
```php
836+
final class ApplicationService
837+
{
838+
public function __construct(private SubscriptionRepositoryInterface $subscriptionRepository) {}
839+
840+
public function renewSubscription(int $subscriptionId): bool
841+
{
842+
$subscription = $this->subscriptionRepository->findById($subscriptionId);
843+
return $subscription->renew(new \DateTimeImmutable());
844+
}
845+
}
846+
```
847+
848+
```php
849+
final class Subscription
850+
{
851+
private Status $status;
852+
853+
private \DateTimeImmutable $modifiedAt;
854+
855+
public function __construct(\DateTimeImmutable $modifiedAt)
856+
{
857+
$this->status = Status::new();
858+
$this->modifiedAt = $modifiedAt;
859+
}
860+
861+
public function renew(\DateTimeImmutable $modifiedAt): bool
862+
{
863+
if (!$this->status->isEqual(Status::expired())) {
864+
return false;
865+
}
866+
867+
$this->status = Status::active();
868+
$this->modifiedAt = $modifiedAt;
869+
return true;
870+
}
871+
872+
public function active(\DateTimeImmutable $modifiedAt): void
873+
{
874+
//simplified
875+
$this->status = Status::active();
876+
$this->modifiedAt = $modifiedAt;
877+
}
878+
879+
public function expire(\DateTimeImmutable $modifiedAt): void
880+
{
881+
//simplified
882+
$this->status = Status::expired();
883+
$this->modifiedAt = $modifiedAt;
884+
}
885+
886+
public function isActive(): bool
887+
{
888+
return $this->status->isEqual(Status::active());
889+
}
890+
}
891+
```
892+
893+
```php
894+
final class ValidTestExample extends TestCase
895+
{
896+
/**
897+
* @test
898+
*/
899+
public function renew_an_expired_subscription_is_possible(): void
900+
{
901+
$expiredSubscription = SubscriptionMother::expired();
902+
$repository = $this->createStub(SubscriptionRepositoryInterface::class);
903+
$repository->method('findById')->willReturn($expiredSubscription);
904+
$sut = new ApplicationService($repository);
905+
906+
$result = $sut->renewSubscription(1);
907+
908+
// skip checking modifiedAt as it's not a part of observable behaviour. To check this value we
909+
// would have to add getter for modifiedAt, probably only for tests purposes.
910+
self::assertTrue($expiredSubscription->isActive());
911+
self::assertTrue($result);
912+
}
913+
914+
/**
915+
* @test
916+
*/
917+
public function renew_an_active_subscription_is_not_possible(): void
918+
{
919+
$activeSubscription = SubscriptionMother::active();
920+
$repository = $this->createStub(SubscriptionRepositoryInterface::class);
921+
$repository->method('findById')->willReturn($activeSubscription);
922+
$sut = new ApplicationService($repository);
923+
924+
$result = $sut->renewSubscription(1);
925+
926+
self::assertTrue($activeSubscription->isActive());
927+
self::assertFalse($result);
928+
}
929+
}
930+
```
931+
736932
## Unit of behaviour
737933

934+
935+
738936
## Humble pattern
739937

740938
## Trivial test

0 commit comments

Comments
 (0)