Peetha's avatar

Retrieved model event not fired in test

Hi there! I'm using PestPHP in order to feature test my API.

I've got the following test in order to check whether an user is synced with the external service:

it('should sync data when external_id present', function () {
    app()->singleton('ExternalService', function () {
        return new MockExternalService();
    });

    $user = user(Role::USER, fake()->uuid());

    $response = $this->actingAs($user, 'api')
        ->json('GET', '/api/users/me');

    $response->assertStatus(200);

    expect($response->getData())
        ->toBeObject()
        ->first_name->not->toBe($user->first_name)
        ->name->not->toBe($user->name);
});

The MockExternalService overwrites the function getContact() so that the external service is not actually called.

class MockExternalService
{
    public function getContact(string $uuid)
    {
        $response = [
            "firstName" => fake()->firstName(),
            "lastName" => fake()->lastName(),
        ];

		// Contact is a DTO
        return Contact::from($response);
    }
}

I've got a User model which uses the trait "Syncable.php". This trait contains the following code in order to sync the object when it's retrieved:

trait Syncable
{
    protected static function bootSyncable()
    {
        static::retrieved(function ($object) {
            $data = $object->fetch() ?? [];
            $object->sync($data);
        });
    }

    public function sync($data = null)
    {
        if ($data) {
            $this->updateQuietly(array_merge($data, ['synced_at' => Carbon::now()]));
        }
    }
}

On my User model I have the fetch() function that retrieves and returns the data from an external service if an external_id exists and 15 minutes have elapsed:

    public function fetch()
    {
        if ($this->external_id && ($this->synced_at ? Carbon::now()->sub('minutes', 15)->isAfter($this->synced_at) : true)) {
            $data = ExternalService::getContact($this->external_id);

			// serialize() transforms the data of the DTO to data that the user model can work with
            return  $data->serialize();
        }
        else {
            return [];
        }
    }

The issue: It looks like the retrieved model event is never fired. When retrieving the current user manually, it sync's the user perfectly. When retrieving the user via the test, the first_name and name property remain the same.

Also executing $user->fetch() in the test will return the mock data that differs from the initial user first_name and name.

Thanks in advance for any help!

0 likes
4 replies
kevinbui's avatar

How do you know the retrieved event is not fired? We can test by putting a logging line or dd:

static::retrieved(function ($object) {
    // Or dd('retrieved event fired');
    \Log::info('retrieved event fired');
    $data = $object->fetch() ?? [];
    $object->sync($data);
});
Peetha's avatar

@kevinbui

Thanks a lot for the reply! Really appreciated!

I tried putting the log message (and also dd) and didn't receive any log (/output in console) when running the test. I did receive logs (/output) when manually calling the /api/users/me endpoint.

Any further ideas on this?

kevinbui's avatar

Hmm, can you share the code of the controller action for that GET /api/users/meAPI?

Peetha's avatar

@kevinbui

class UserController extends Controller
{
    public function show(ShowUserRequest $request, User $user): UserResource
    {
        return new UserResource($user);
    }
}
class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'uuid' => $this->uuid,
            'email' => $this->email,
            'first_name' => $this->first_name,
            'name' => $this->name,
	        ];
    }
}

Please or to participate in this conversation.