Bruno Francisco
Bruno Francisco

Reputation: 4220

Factory relationship returning null laravel testing

I'm trying to unit testing a service that handles the registration of a user in Laravel.

This is the service:

public function completeRegistration(Collection $data)
    {
        $code = $data->get('code');
        $registerToken = $this->fetchRegisterToken($code);

        DB::beginTransaction();

        $registerToken->update(['used_at' => Carbon::now()]);
        $user = $this->userRepository->update($data, $registerToken->user);
        $token = $user->createToken(self::DEFAULT_TOKEN_NAME);

        DB::commit();

        return [
            'user' => $user,
            'token' => $token->plainTextToken,
        ];
    }

Where the update method has the following signature:

<?php

namespace App\Repositories\User;

use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

interface UserRepositoryInterface
{
    public function create(Collection $data): User;
    public function update(Collection $data, User $user): User;
}

With my test being:

/**
     * Test a user can register
     *
     * @return void
     */
    public function test_user_can_complete_registration()
    {
        $userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
        $registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);
        $userFactory = User::factory()->make();
        $registerTokenFactory = RegisterToken::factory()
            ->for($userFactory)
            ->timestamped()
            ->make(['user_id' => $userFactory->id]);

        dd($registerTokenFactory->user);

        $userRepositoryMock
            ->expects($this->any())
            ->once()
            ->andReturn($userFactory);

        ....

    }

When I run phpunit --filter=test_user_can_complete_registration I get the following error:

1) Tests\Unit\Services\Auth\AuthServiceTest::test_user_can_complete_registration
TypeError: Argument 2 passed to Mockery_0_App_Repositories_User_UserRepositoryInterface::update() must be an instance of App\Models\User, null given, called in /var/www/app/Services/Auth/AuthService.php on line 64

/var/www/app/Services/Auth/AuthService.php:64
/var/www/tests/Unit/Services/Auth/AuthServiceTest.php:88

This tells me that the user relationship on $registerTokenFactory is null. When I do:

public function test_user_can_complete_registration()
{
   ...
   dd($registerTokenFactory->user);
}

I get the output null. I'm trying to test the service without hitting the database. How can I attach the user relationship to the $registerTokenFactory object? I have tried using for and trying to attach directly:

$registerTokenFactory = RegisterToken::factory()
            ->for($userFactory)
            ->timestamped()
            ->make(['user_id' => $userFactory->id, 'user' => $userFactory]);

Upvotes: 2

Views: 1307

Answers (1)

mrhn
mrhn

Reputation: 18916

In Laravel factories make() does only create the model and does not save it. For relationship to work, you will need your models to be saved.

   $userFactory = User::factory()->create();

Since you do not want to use a Database, which is wrong in my opinion. People don't like writing tests, so when we have to do it make it simple, mocking everything to avoid databases is a pain. Instead an alternative is to you Sqlite to run in memory, fast and easy. A drawback is some functionality does not work there JSON fields and the version that are in most Ubuntu distributions does not respect foreign keys.

If you want to follow the path you are already on, assigned the user on the object would work, you have some left out bits of the code i assume.

$userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
$registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);

$user = User::factory()->make();

$registerToken = RegisterToken::factory()
    ->for($userFactory)
    ->timestamped()
    ->make(['user_id' => $user->id]);

$registerToken->user = $user;

$registerTokenRepositoryMock
    ->expects('fetchRegisterToken')
    ->once()
    ->andReturn($registerToken);

$userRepositoryMock
    ->expects($this->any())
    ->once()
    ->andReturn($user);

// execute the call

Upvotes: 2

Related Questions