Unit tests on application services
This commit is contained in:
20
.github/workflows/manuscript.yaml
vendored
20
.github/workflows/manuscript.yaml
vendored
@@ -1,20 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.16.0
|
||||
with:
|
||||
php-version: 8.1
|
||||
extensions: xdebug, redis, amqp
|
||||
- name: Composer install
|
||||
run: php composer.phar install --ignore-platform-req=php
|
||||
- name: Run tests
|
||||
run: php composer.phar unit-tests
|
||||
- name: Run Infection
|
||||
run: php infection.phar --min-msi=80 --min-covered-msi=70 --threads=4 --show-mutations --only-covered
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -7,14 +7,9 @@
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> application ###
|
||||
infection.log
|
||||
coverage
|
||||
###< application ###
|
||||
###> php-cs-fixer ###
|
||||
.php-cs-fixer.cache
|
||||
@@ -27,4 +22,8 @@ infection.log
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
###< yarn ###
|
||||
###< yarn ###
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
19
bin/phpunit
Executable file
19
bin/phpunit
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
||||
96945
bin/phpunit.phar
96945
bin/phpunit.phar
File diff suppressed because one or more lines are too long
@@ -51,9 +51,11 @@
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.0.*",
|
||||
"symfony/css-selector": "6.0.*",
|
||||
"symfony/debug-bundle": "6.0.*",
|
||||
"symfony/maker-bundle": "^1.14",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/phpunit-bridge": "^6.1",
|
||||
"symfony/stopwatch": "6.0.*",
|
||||
"symfony/web-profiler-bundle": "6.0.*",
|
||||
"theofidry/psysh-bundle": "^4.3"
|
||||
|
||||
150
composer.lock
generated
150
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "956cf7681153f66649fb80d9594534a4",
|
||||
"content-hash": "f213d9744da79d8e4893aea0fcce90d0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
@@ -10074,6 +10074,71 @@
|
||||
],
|
||||
"time": "2022-01-02T09:55:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v6.0.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ab2746acddc4f03a7234c8441822ac5d5c63efe9",
|
||||
"reference": "ab2746acddc4f03a7234c8441822ac5d5c63efe9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\CssSelector\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Jean-François Simon",
|
||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS selectors to XPath expressions",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/v6.0.11"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-06-27T17:10:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug-bundle",
|
||||
"version": "v6.0.3",
|
||||
@@ -10477,6 +10542,89 @@
|
||||
],
|
||||
"time": "2021-11-05T10:34:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/phpunit-bridge",
|
||||
"version": "v6.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/phpunit-bridge.git",
|
||||
"reference": "75c2fa71d049c1f48e39d208c0cefba97e66335a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/75c2fa71d049c1f48e39d208c0cefba97e66335a",
|
||||
"reference": "75c2fa71d049c1f48e39d208c0cefba97e66335a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<7.5|9.1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/deprecation-contracts": "^2.1|^3.0",
|
||||
"symfony/error-handler": "^5.4|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
|
||||
},
|
||||
"bin": [
|
||||
"bin/simple-phpunit"
|
||||
],
|
||||
"type": "symfony-bridge",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "phpunit/phpunit",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Bridge\\PhpUnit\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/phpunit-bridge/tree/v6.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-07-28T13:40:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/web-profiler-bundle",
|
||||
"version": "v6.0.6",
|
||||
|
||||
@@ -30,23 +30,22 @@ final class FollowApplicationService
|
||||
|
||||
public function countFollowersOf(string $authorId): int
|
||||
{
|
||||
$authorId = AuthorId::fromString($authorId);
|
||||
$this->tryToFindAuthor($authorId);
|
||||
|
||||
if (null === $this->authorRepository->ofId($authorId)) {
|
||||
throw AuthorDoesNotExist::withAuthorIdOf($authorId);
|
||||
}
|
||||
|
||||
return $this->followRepository->numberOfFollowersFor($authorId);
|
||||
return $this->followRepository->numberOfFollowersFor(
|
||||
AuthorId::fromString($authorId)
|
||||
);
|
||||
}
|
||||
|
||||
private function tryToFindAuthor(string $authorId): Author
|
||||
{
|
||||
$fromAuthor = $this->authorRepository->ofId(AuthorId::fromString($authorId));
|
||||
$id = AuthorId::fromString($authorId);
|
||||
$author = $this->authorRepository->ofId($id);
|
||||
|
||||
if (null === $fromAuthor) {
|
||||
throw AuthorDoesNotExist::withAuthorIdOf(AuthorId::fromString($authorId));
|
||||
if (null === $author) {
|
||||
throw AuthorDoesNotExist::withAuthorIdOf($id);
|
||||
}
|
||||
|
||||
return $fromAuthor;
|
||||
return $author;
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,6 @@ interface CheepRepository
|
||||
/** @return Cheep[] */
|
||||
public function all(): array;
|
||||
|
||||
/** @return Cheep[] */
|
||||
public function allBetween(DateTimeInterface $from, DateTimeInterface $to): array;
|
||||
|
||||
/** @return Cheep[] */
|
||||
public function allGroupedByMonthAndYear(): array;
|
||||
|
||||
/** @return Cheep[] */
|
||||
public function ofFollowersOfAuthor(Author $author): array;
|
||||
|
||||
/** @return Cheep[] */
|
||||
public function groupedByMonth(int $year): array;
|
||||
|
||||
/** @return Cheep[] */
|
||||
public function ofFollowingPeopleOf(Author $author, int $offset, int $size): array;
|
||||
}
|
||||
|
||||
@@ -34,26 +34,6 @@ final class DoctrineOrmCheepRepository implements CheepRepository
|
||||
return $this->em->getRepository(Cheep::class)->findAll();
|
||||
}
|
||||
|
||||
public function allBetween(DateTimeInterface $from, DateTimeInterface $to): array
|
||||
{
|
||||
// TODO: Implement allBetween() method.
|
||||
}
|
||||
|
||||
public function allGroupedByMonthAndYear(): array
|
||||
{
|
||||
// TODO: Implement allGroupedByMonthAndYear() method.
|
||||
}
|
||||
|
||||
public function ofFollowersOfAuthor(Author $author): array
|
||||
{
|
||||
// TODO: Implement ofFollowersOfAuthor() method.
|
||||
}
|
||||
|
||||
public function groupedByMonth(int $year): array
|
||||
{
|
||||
// TODO: Implement groupedByMonth() method.
|
||||
}
|
||||
|
||||
public function ofFollowingPeopleOf(Author $author, int $offset, int $size): array
|
||||
{
|
||||
$dql = <<<DQL
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\Infrastructure\Persistence;
|
||||
|
||||
use Cheeper\DomainModel\Author\AuthorId;
|
||||
use Cheeper\DomainModel\Follow\Follow;
|
||||
use Cheeper\DomainModel\Follow\FollowRepository;
|
||||
|
||||
final class InMemoryFollowRepository implements FollowRepository
|
||||
{
|
||||
/** @var Follow[] */
|
||||
private array $follows = [];
|
||||
|
||||
public function numberOfFollowersFor(AuthorId $authorId): int
|
||||
{
|
||||
return count(
|
||||
array_filter(
|
||||
$this->follows,
|
||||
static fn(Follow $f) => $f->toAuthorId()->equals($authorId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function add(Follow $follow): void
|
||||
{
|
||||
$this->follows[] = $follow;
|
||||
}
|
||||
|
||||
public function fromAuthorIdAndToAuthorId(AuthorId $fromAuthorId, AuthorId $toAuthorId): ?Follow
|
||||
{
|
||||
$candidates = array_filter(
|
||||
$this->follows,
|
||||
static fn(Follow $f) => $f->fromAuthorId()->equals($fromAuthorId) && $f->toAuthorId()->equals($toAuthorId)
|
||||
);
|
||||
|
||||
return current($candidates);
|
||||
}
|
||||
|
||||
public function toAuthorId(AuthorId $authorId): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->follows,
|
||||
static fn(Follow $f) => $f->toAuthorId()->equals($authorId)
|
||||
);
|
||||
}
|
||||
}
|
||||
18
symfony.lock
18
symfony.lock
@@ -330,6 +330,9 @@
|
||||
"config/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/css-selector": {
|
||||
"version": "v6.0.11"
|
||||
},
|
||||
"symfony/debug-bundle": {
|
||||
"version": "4.1",
|
||||
"recipe": {
|
||||
@@ -448,6 +451,21 @@
|
||||
"symfony/password-hasher": {
|
||||
"version": "v5.3.7"
|
||||
},
|
||||
"symfony/phpunit-bridge": {
|
||||
"version": "6.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "97cb3dc7b0f39c7cfc4b7553504c9d7b7795de96"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"bin/phpunit",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/polyfill-intl-grapheme": {
|
||||
"version": "v1.17.0"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,15 @@ use Ramsey\Uuid\Uuid;
|
||||
|
||||
final class AuthorApplicationServiceTest extends TestCase
|
||||
{
|
||||
private AuthorApplicationService $authorApplicationService;
|
||||
private AuthorRepository $authorRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->authorRepository = new InMemoryAuthorRepository();
|
||||
$this->authorApplicationService = new AuthorApplicationService($this->authorRepository);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenAuthorSignUpWhenAuthorUsernameIsAlreadyPickedUpThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
@@ -30,13 +39,11 @@ final class AuthorApplicationServiceTest extends TestCase
|
||||
$website = 'https://google.com';
|
||||
$birthDate = (new \DateTimeImmutable())->format('Y-m-d');
|
||||
|
||||
$authorRepository = new InMemoryAuthorRepository();
|
||||
$authorRepository->add(
|
||||
$this->authorRepository->add(
|
||||
AuthorTestDataBuilder::anAuthor()->build()
|
||||
);
|
||||
|
||||
$authorApplicationService = new AuthorApplicationService($authorRepository);
|
||||
$authorApplicationService->signUp(
|
||||
$this->authorApplicationService->signUp(
|
||||
$id,
|
||||
$username,
|
||||
$email,
|
||||
@@ -60,10 +67,7 @@ final class AuthorApplicationServiceTest extends TestCase
|
||||
$website = 'https://google.com';
|
||||
$birthDate = (new \DateTimeImmutable())->format('Y-m-d');
|
||||
|
||||
$authorRepository = new InMemoryAuthorRepository();
|
||||
|
||||
$authorApplicationService = new AuthorApplicationService($authorRepository);
|
||||
$authorApplicationService->signUp(
|
||||
$this->authorApplicationService->signUp(
|
||||
$id,
|
||||
$username,
|
||||
$email,
|
||||
@@ -75,7 +79,7 @@ final class AuthorApplicationServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$this->assertNotNull(
|
||||
$authorRepository->ofUserName(UserName::pick('irrelevant'))
|
||||
$this->authorRepository->ofUserName(UserName::pick('irrelevant'))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,21 +11,27 @@ use Cheeper\DomainModel\Author\AuthorId;
|
||||
use Cheeper\DomainModel\Author\AuthorRepository;
|
||||
use Cheeper\DomainModel\Author\EmailAddress;
|
||||
use Cheeper\DomainModel\Author\UserName;
|
||||
use Cheeper\DomainModel\Cheep\Cheep;
|
||||
use Cheeper\DomainModel\Cheep\CheepId;
|
||||
use Cheeper\DomainModel\Cheep\CheepMessage;
|
||||
use Cheeper\DomainModel\Cheep\CheepRepository;
|
||||
use Cheeper\Infrastructure\Persistence\InMemoryAuthorRepository;
|
||||
use Cheeper\Tests\DomainModel\Author\AuthorTestDataBuilder;
|
||||
use Cheeper\Tests\DomainModel\Cheep\CheepTestDataBuilder;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CheepApplicationServiceTest extends TestCase
|
||||
{
|
||||
private CheepRepository $cheeps;
|
||||
private CheepRepository $cheepRepository;
|
||||
private AuthorRepository $authorRepository;
|
||||
private CheepApplicationService $cheepService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->cheeps = Mockery::mock(CheepRepository::class);
|
||||
$this->authorRepository = Mockery::mock(AuthorRepository::class);
|
||||
$this->cheepService = new CheepApplicationService($this->authorRepository, $this->cheeps);
|
||||
$this->cheepRepository = Mockery::mock(CheepRepository::class);
|
||||
$this->authorRepository = new InMemoryAuthorRepository();
|
||||
$this->cheepService = new CheepApplicationService($this->authorRepository, $this->cheepRepository);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
@@ -33,16 +39,17 @@ final class CheepApplicationServiceTest extends TestCase
|
||||
{
|
||||
$this->expectException(AuthorDoesNotExist::class);
|
||||
|
||||
$this->authorRepository->allows('ofUserName')->andReturns(null);
|
||||
|
||||
$this->cheepService->postCheep('irrelevant', 'irrelevant');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function itShouldAddCheep(): void
|
||||
{
|
||||
$this->authorRepository->allows('ofUsername')->andReturns(self::anAuthor());
|
||||
$this->cheeps->expects('add');
|
||||
$this->authorRepository->add(
|
||||
AuthorTestDataBuilder::anAuthor()->build()
|
||||
);
|
||||
|
||||
$this->cheepRepository->expects('add');
|
||||
|
||||
$cheep = $this->cheepService->postCheep('irrelevant', 'message');
|
||||
|
||||
@@ -51,12 +58,32 @@ final class CheepApplicationServiceTest extends TestCase
|
||||
$this->assertEquals('message', $cheep->cheepMessage()->message());
|
||||
}
|
||||
|
||||
private static function anAuthor(): Author
|
||||
/** @test */
|
||||
public function givenATimelineRequestWhenTheAuthorDoesNotExistThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
return Author::signUp(
|
||||
AuthorId::nextIdentity(),
|
||||
UserName::pick('irrelevant'),
|
||||
EmailAddress::from('test@gmail.com')
|
||||
$this->expectException(AuthorDoesNotExist::class);
|
||||
|
||||
$this->cheepService->timelineFrom(AuthorTestDataBuilder::anAuthorIdentity()->toString(), 0, 1);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenATimelineRequestWhenExecutionGoesWellThenAListOfCheepsShouldBeReturned(): void
|
||||
{
|
||||
$author = AuthorTestDataBuilder::anAuthor()->build();
|
||||
|
||||
$this->authorRepository->add($author);
|
||||
|
||||
$cheeps = [
|
||||
CheepTestDataBuilder::aCheep()->withAMessage("test1"),
|
||||
CheepTestDataBuilder::aCheep()->withAMessage("test2"),
|
||||
CheepTestDataBuilder::aCheep()->withAMessage("test3"),
|
||||
];
|
||||
|
||||
$this->cheepRepository->allows()->ofFollowingPeopleOf($author, 0, 10)->andReturn($cheeps);
|
||||
|
||||
$this->assertCount(
|
||||
count($cheeps),
|
||||
$this->cheepService->timelineFrom($author->authorId()->toString(), 0, 10)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
107
tests/Cheeper/Tests/Application/FollowApplicationServiceTest.php
Normal file
107
tests/Cheeper/Tests/Application/FollowApplicationServiceTest.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\Tests\Application;
|
||||
|
||||
use Cheeper\Application\FollowApplicationService;
|
||||
use Cheeper\DomainModel\Author\AuthorDoesNotExist;
|
||||
use Cheeper\DomainModel\Author\AuthorRepository;
|
||||
use Cheeper\DomainModel\Follow\FollowRepository;
|
||||
use Cheeper\Infrastructure\Persistence\InMemoryAuthorRepository;
|
||||
use Cheeper\Infrastructure\Persistence\InMemoryFollowRepository;
|
||||
use Cheeper\Tests\DomainModel\Author\AuthorTestDataBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class FollowApplicationServiceTest extends TestCase
|
||||
{
|
||||
private AuthorRepository $authorRepository;
|
||||
private FollowApplicationService $followApplicationService;
|
||||
private FollowRepository $followRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->authorRepository = new InMemoryAuthorRepository();
|
||||
$this->followRepository = new InMemoryFollowRepository();
|
||||
|
||||
$this->followApplicationService = new FollowApplicationService($this->followRepository, $this->authorRepository);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenANonExistingAuthorWhenFollowUseCaseIsExecutedThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
$this->expectException(AuthorDoesNotExist::class);
|
||||
|
||||
$this->followApplicationService->followTo(
|
||||
AuthorTestDataBuilder::anAuthorIdentity()->toString(),
|
||||
AuthorTestDataBuilder::anAuthorIdentity()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenANonExistingAuthorToFollowWhenFollowUseCaseIsExecutedThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
$this->expectException(AuthorDoesNotExist::class);
|
||||
|
||||
$author = AuthorTestDataBuilder::anAuthor()->build();
|
||||
|
||||
$this->authorRepository->add($author);
|
||||
|
||||
$this->followApplicationService->followTo(
|
||||
$author->authorId()->toString(),
|
||||
AuthorTestDataBuilder::anAuthorIdentity()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenTwoExistingAuthorsWhenFollowUseCaseIsExecutedThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
$fromAuthor = AuthorTestDataBuilder::anAuthor()->build();
|
||||
$toAuthor = AuthorTestDataBuilder::anAuthor()->build();
|
||||
|
||||
$this->authorRepository->add($fromAuthor);
|
||||
$this->authorRepository->add($toAuthor);
|
||||
|
||||
$this->followApplicationService->followTo(
|
||||
$fromAuthor->authorId()->toString(),
|
||||
$toAuthor->authorId()->toString()
|
||||
);
|
||||
|
||||
$follows = $this->followRepository->toAuthorId($toAuthor->authorId());
|
||||
|
||||
$this->assertCount(1, $follows);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenFollowersCountForANonExistingAuthorWhenCountIsRequestedThenAnExceptionShouldBeThrown(): void
|
||||
{
|
||||
$this->expectException(AuthorDoesNotExist::class);
|
||||
|
||||
$this->followApplicationService->countFollowersOf(
|
||||
AuthorTestDataBuilder::anAuthorIdentity()->toString()
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function givenFollowersCountWhenCountIsRequestedThenItShouldReturnTheTotalNumberOfFollowers(): void
|
||||
{
|
||||
$author = AuthorTestDataBuilder::anAuthor()->build();
|
||||
|
||||
$follower1 = AuthorTestDataBuilder::anAuthor()->build();
|
||||
$follower2 = AuthorTestDataBuilder::anAuthor()->build();
|
||||
$follower3 = AuthorTestDataBuilder::anAuthor()->build();
|
||||
|
||||
$this->authorRepository->add($author);
|
||||
$this->authorRepository->add($follower1);
|
||||
$this->authorRepository->add($follower2);
|
||||
$this->authorRepository->add($follower3);
|
||||
|
||||
$this->followApplicationService->followTo($follower1->authorId()->toString(), $author->authorId()->toString());
|
||||
$this->followApplicationService->followTo($follower2->authorId()->toString(), $author->authorId()->toString());
|
||||
$this->followApplicationService->followTo($follower3->authorId()->toString(), $author->authorId()->toString());
|
||||
|
||||
$totalNumberOfFollowers = $this->followApplicationService->countFollowersOf($author->authorId()->toString());
|
||||
|
||||
$this->assertSame(3, $totalNumberOfFollowers);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ use Cheeper\DomainModel\Author\BirthDate;
|
||||
use Cheeper\DomainModel\Author\EmailAddress;
|
||||
use Cheeper\DomainModel\Author\UserName;
|
||||
use Cheeper\DomainModel\Author\Website;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
final class AuthorTestDataBuilder
|
||||
{
|
||||
@@ -30,6 +32,19 @@ final class AuthorTestDataBuilder
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function anAuthorIdentity(string | UuidInterface | null $anAuthorId = null): AuthorId
|
||||
{
|
||||
if ($anAuthorId && is_string($anAuthorId)) {
|
||||
return AuthorId::fromString($anAuthorId);
|
||||
}
|
||||
|
||||
if ($anAuthorId) {
|
||||
return AuthorId::fromUuid($anAuthorId);
|
||||
}
|
||||
|
||||
return AuthorId::nextIdentity();
|
||||
}
|
||||
|
||||
public function withUserNameOf(string $userName): self
|
||||
{
|
||||
$this->userName = $userName;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Cheeper\Tests\DomainModel\Cheep;
|
||||
|
||||
use Cheeper\DomainModel\Cheep\Cheep;
|
||||
use Cheeper\DomainModel\Cheep\CheepId;
|
||||
use Cheeper\DomainModel\Cheep\CheepMessage;
|
||||
use Cheeper\Tests\DomainModel\Author\AuthorTestDataBuilder;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
final class CheepTestDataBuilder
|
||||
{
|
||||
private string | UuidInterface | null $authorId = null;
|
||||
private string | UuidInterface | null $cheepId = null;
|
||||
private string $cheepMessage;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function aCheepIdentity(string | UuidInterface | null $aCheepId = null): CheepId
|
||||
{
|
||||
if ($aCheepId && is_string($aCheepId)) {
|
||||
return CheepId::fromString($aCheepId);
|
||||
}
|
||||
|
||||
if ($aCheepId) {
|
||||
return CheepId::fromUuid($aCheepId);
|
||||
}
|
||||
|
||||
return CheepId::nextIdentity();
|
||||
}
|
||||
|
||||
public static function aCheep(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function fromAuthorId(string|UuidInterface $authorId): self
|
||||
{
|
||||
$this->authorId = $authorId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withCheepIdOf(string|UuidInterface $cheepId): self
|
||||
{
|
||||
$this->cheepId = $cheepId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAMessage(string $message): self
|
||||
{
|
||||
$this->cheepMessage = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): Cheep
|
||||
{
|
||||
return Cheep::compose(
|
||||
AuthorTestDataBuilder::anAuthorIdentity($this->authorId),
|
||||
self::aCheepIdentity($this->cheepId),
|
||||
CheepMessage::write($this->cheepMessage)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user