From 416137f885c9ade30fbc107292403c547500d1ea Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 29 Jun 2026 15:38:03 +0900 Subject: [PATCH 1/5] feat: add findById signature to UserRepositroyInterface --- src/app/Packages/Domains/Interface/UserRepositroyInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/Packages/Domains/Interface/UserRepositroyInterface.php b/src/app/Packages/Domains/Interface/UserRepositroyInterface.php index ab2f3b0..cad8f5e 100644 --- a/src/app/Packages/Domains/Interface/UserRepositroyInterface.php +++ b/src/app/Packages/Domains/Interface/UserRepositroyInterface.php @@ -9,5 +9,7 @@ interface UserRepositroyInterface { public function createUser(UserEntity $entity): UserDto; + public function findById(int $id): ?UserDto; + public function updateUser(UserEntity $entity): UserDto; } \ No newline at end of file From 11a15b5f382a2baf42890050bd4c25e5bad29dc7 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 29 Jun 2026 15:38:08 +0900 Subject: [PATCH 2/5] feat: implement findById in UserRepository --- src/app/Packages/Domains/UserRepository.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/Packages/Domains/UserRepository.php b/src/app/Packages/Domains/UserRepository.php index 3b011df..87166cd 100644 --- a/src/app/Packages/Domains/UserRepository.php +++ b/src/app/Packages/Domains/UserRepository.php @@ -34,6 +34,17 @@ public function createUser(UserEntity $entity): UserDto return UserDtoFactory::build($insertUser->toArray()); } + public function findById(int $id): ?UserDto + { + $user = $this->userModel->find($id); + + if ($user === null) { + return null; + } + + return UserDtoFactory::build($user->toArray()); + } + public function updateUser(UserEntity $entity): UserDto { $user = $this->userModel->find($entity->getId()); From 5abc5e2e01e02807311cc5b5d6063207dac48a2d Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 29 Jun 2026 15:38:20 +0900 Subject: [PATCH 3/5] test: rewrite UserRepositoryTest as DB integration tests --- .../Domains/Tests/UserRepositoryTest.php | 147 ++++++++---------- 1 file changed, 61 insertions(+), 86 deletions(-) diff --git a/src/app/Packages/Domains/Tests/UserRepositoryTest.php b/src/app/Packages/Domains/Tests/UserRepositoryTest.php index 1801cf1..9262cfd 100644 --- a/src/app/Packages/Domains/Tests/UserRepositoryTest.php +++ b/src/app/Packages/Domains/Tests/UserRepositoryTest.php @@ -3,147 +3,122 @@ namespace App\Packages\Domains\Tests; use App\Models\User; -use App\Packages\Domains\UserRepository; +use App\Packages\Domains\User\AgeRange; use App\Packages\Domains\User\Factory\UserEntityFactory; +use App\Packages\Domains\User\Subscription\Subscription; +use App\Packages\Domains\User\Subscription\SubscriptionTier; use App\Packages\Domains\User\UserEntity; +use App\Packages\Domains\UserRepository; use App\Packages\Features\QueryUseCases\Dto\User\UserDto; -use Exception; use Faker\Factory as FakerFactory; -use Mockery; -use Mockery\MockInterface; -use PHPUnit\Framework\TestCase; +use Illuminate\Support\Facades\DB; +use Tests\TestCase; class UserRepositoryTest extends TestCase { + protected function setUp(): void + { + parent::setUp(); + $this->truncate(); + } + protected function tearDown(): void { - Mockery::close(); + $this->truncate(); parent::tearDown(); } - private function buildEntity(): UserEntity + private function truncate(): void + { + if (env('APP_ENV') === 'testing') { + DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=0;'); + User::truncate(); + DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=1;'); + } + } + + private function repository(): UserRepository + { + return new UserRepository(new User()); + } + + private function seedUser(array $overrides = []): User { $faker = FakerFactory::create(); - return UserEntityFactory::build([ - 'id' => null, + return User::create(array_merge([ 'first_name' => $faker->firstName(), 'last_name' => $faker->lastName(), 'email' => $faker->unique()->safeEmail(), - 'age_range' => $faker->randomElement(['teens', '20s', '30s', '40s', '50s', '60plus']), - 'subscription_tier' => $faker->randomElement(['free', 'premium']), + 'password' => bcrypt('password'), + 'age_range' => 'teens', + 'subscription_tier' => 'free', 'subscription_expires_at' => null, - 'password_hash' => 'hashed_password', - ]); + ], $overrides)); } - private function buildAttributes(): array + private function buildEntity(array $overrides = []): UserEntity { $faker = FakerFactory::create(); - return [ - 'id' => $faker->unique()->randomNumber(5), + return UserEntityFactory::build(array_merge([ + 'id' => null, 'first_name' => $faker->firstName(), 'last_name' => $faker->lastName(), 'email' => $faker->unique()->safeEmail(), - 'age_range' => $faker->randomElement(['teens', '20s', '30s', '40s', '50s', '60plus']), + 'age_range' => 'teens', 'subscription_tier' => 'free', 'subscription_expires_at' => null, - ]; - } - - private function buildCreateModelMock(bool $wasRecentlyCreated): User - { - $attributes = $this->buildAttributes(); - - /** @var User|MockInterface $createdUser */ - $createdUser = Mockery::mock(User::class); - $createdUser->wasRecentlyCreated = $wasRecentlyCreated; - $createdUser->shouldReceive('toArray')->andReturn($attributes); - - /** @var User|MockInterface $userModel */ - $userModel = Mockery::mock(User::class); - $userModel->shouldReceive('create')->andReturn($createdUser); - - return $userModel; + 'password_hash' => bcrypt('password'), + ], $overrides)); } - private function buildUpdateModelMock(?int $foundId): User + public function test_createUser_persists_user_and_returns_dto(): void { - $attributes = $this->buildAttributes(); + $result = $this->repository()->createUser($this->buildEntity()); - /** @var User|MockInterface $userModel */ - $userModel = Mockery::mock(User::class); - - if ($foundId === null) { - $userModel->shouldReceive('find')->andReturn(null); - } else { - /** @var User|MockInterface $foundUser */ - $foundUser = Mockery::mock(User::class)->makePartial(); - $foundUser->shouldReceive('update')->andReturn(true); - $foundUser->shouldReceive('refresh')->andReturn(null); - $foundUser->shouldReceive('toArray')->andReturn($attributes); - - $userModel->shouldReceive('find')->with($foundId)->andReturn($foundUser); - } - - return $userModel; + $this->assertInstanceOf(UserDto::class, $result); + $this->assertDatabaseHas('users', ['email' => $result->email]); } - public function test_createUser_returns_user_dto_on_success(): void + public function test_findById_returns_user_dto_when_user_exists(): void { - $repository = new UserRepository($this->buildCreateModelMock(wasRecentlyCreated: true)); - $result = $repository->createUser($this->buildEntity()); + $user = $this->seedUser(); + $result = $this->repository()->findById($user->id); $this->assertInstanceOf(UserDto::class, $result); + $this->assertSame($user->id, $result->id); + $this->assertSame($user->email, $result->email); } - public function test_createUser_throws_exception_when_insert_fails(): void + public function test_findById_returns_null_when_user_not_found(): void { - $repository = new UserRepository($this->buildCreateModelMock(wasRecentlyCreated: false)); + $result = $this->repository()->findById(999999); - $this->expectException(Exception::class); - $this->expectExceptionMessage('Failed to create user.'); - - $repository->createUser($this->buildEntity()); + $this->assertNull($result); } - public function test_updateUser_returns_user_dto_on_success(): void + public function test_updateUser_persists_changes_and_returns_dto(): void { - $existingId = 1; - $entity = UserEntityFactory::build([ - 'id' => $existingId, - 'first_name' => 'Updated', - 'last_name' => 'Name', - 'email' => 'updated@example.com', - 'age_range' => 'teens', - 'subscription_tier' => 'free', - 'subscription_expires_at' => null, + $user = $this->seedUser(['email' => 'before@example.com']); + $entity = $this->buildEntity([ + 'id' => $user->id, + 'email' => 'after@example.com', ]); - $repository = new UserRepository($this->buildUpdateModelMock(foundId: $existingId)); - $result = $repository->updateUser($entity); + $result = $this->repository()->updateUser($entity); $this->assertInstanceOf(UserDto::class, $result); + $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => 'after@example.com']); } public function test_updateUser_throws_exception_when_user_not_found(): void { - $entity = UserEntityFactory::build([ - 'id' => 999, - 'first_name' => 'Ghost', - 'last_name' => 'User', - 'email' => 'ghost@example.com', - 'age_range' => 'teens', - 'subscription_tier' => 'free', - 'subscription_expires_at' => null, - ]); - - $repository = new UserRepository($this->buildUpdateModelMock(foundId: null)); + $entity = $this->buildEntity(['id' => 999999]); - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $this->expectExceptionMessage('User not found.'); - $repository->updateUser($entity); + $this->repository()->updateUser($entity); } } \ No newline at end of file From 6de59b43898f3eb58bc8b14c316afdf793033f91 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 29 Jun 2026 21:04:35 +0900 Subject: [PATCH 4/5] feat: implement GetUserByIdUseCase --- .../UseCase/User/GetUserByIdUseCase.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/app/Packages/Features/QueryUseCases/UseCase/User/GetUserByIdUseCase.php diff --git a/src/app/Packages/Features/QueryUseCases/UseCase/User/GetUserByIdUseCase.php b/src/app/Packages/Features/QueryUseCases/UseCase/User/GetUserByIdUseCase.php new file mode 100644 index 0000000..1fe1893 --- /dev/null +++ b/src/app/Packages/Features/QueryUseCases/UseCase/User/GetUserByIdUseCase.php @@ -0,0 +1,25 @@ +userRepository->findById($id); + + if ($userDto === null) { + throw new Exception('User not found.'); + } + + return $userDto; + } +} From ef5b06263e6cef20712d25bd85d59390405000b5 Mon Sep 17 00:00:00 2001 From: Application-drop-up Date: Mon, 29 Jun 2026 21:04:42 +0900 Subject: [PATCH 5/5] test: add unit tests for GetUserByIdUseCase --- .../Tests/UseCase/GetUserByIdUseCaseTest.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/app/Packages/Features/QueryUseCases/Tests/UseCase/GetUserByIdUseCaseTest.php diff --git a/src/app/Packages/Features/QueryUseCases/Tests/UseCase/GetUserByIdUseCaseTest.php b/src/app/Packages/Features/QueryUseCases/Tests/UseCase/GetUserByIdUseCaseTest.php new file mode 100644 index 0000000..42e71fc --- /dev/null +++ b/src/app/Packages/Features/QueryUseCases/Tests/UseCase/GetUserByIdUseCaseTest.php @@ -0,0 +1,67 @@ +unique()->randomNumber(5), + firstName: $faker->firstName(), + lastName: $faker->lastName(), + email: $faker->unique()->safeEmail(), + ageRange: 'teens', + subscriptionTier: 'free', + subscriptionExpiresAt: null, + ); + } + + public function test_handle_returns_user_dto_when_user_exists(): void + { + $dto = $this->buildDto(); + + /** @var UserRepositroyInterface|MockInterface $repository */ + $repository = Mockery::mock(UserRepositroyInterface::class); + $repository->shouldReceive('findById') + ->once() + ->with($dto->id) + ->andReturn($dto); + + $result = (new GetUserByIdUseCase($repository))->handle($dto->id); + + $this->assertSame($dto, $result); + } + + public function test_handle_throws_exception_when_user_not_found(): void + { + /** @var UserRepositroyInterface|MockInterface $repository */ + $repository = Mockery::mock(UserRepositroyInterface::class); + $repository->shouldReceive('findById') + ->once() + ->with(999) + ->andReturn(null); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('User not found.'); + + (new GetUserByIdUseCase($repository))->handle(999); + } +}