Search code examples
phplaravelphpuniteager-loading

Laravel eager loading phpunit testing


I am trying to do a GET request to retrieve a specific film via the id /GET 'film/{id}' etc

film_table

-- id

-- description

this is what is part of the response, what if I've many-to-many relationships within the film model as shown in the film model

public function languages(): BelongsToMany
{
    return $this->belongsToMany(
        'App\Models\Language',
        'film_languages',
        'film_id',
        'language_code'
    );
}

public function categories(): BelongsToMany
{
    return $this->belongsToMany(
        'App\Models\Category',
        'film_categories',
        'film_id',
        'category_id'
    );
}

is there a way you can include these tables as part of the response I came across something called eager loading in the Laravel docs but unsure if this covers what I need I have an example below but I'm lost if I'm on the right tracks?

FilmController

public function show(string $id): string
{
    /** @var Film  $film*/
    $film = film::findOrFail($id)
        ->with(
            'languages',
            'categories',
            )->get();

    return $film->toJson();

I am trying to write a PHPUnit test to ensure that when a film is grabbed via the id it returns the id, description as well as the options choosing from the many-to-many relations.

This is the test I've started

FilmControllerTest

public function setUp(): void
{
    parent::setUp();

    $this->film = factory(Film::class)->create();

    $languages = factory(Language::class)->create();

    $categories = factory(Category::class)->create();
}

/**
 * @test
 */
public function it_should_get_film()
{
    $response = $this->json('GET', '/film/' . $this->film->id);

    $film= Film::findOrFail($this->film->id);

    $response->assertJson([
        'id' => $film->id,
        'description' => 'This is my favourite film'
    ]);
}

can you include these relationships into a factory?

FilmFactory

    $factory->define(\App\Models\Film::class, function (Faker $faker) {
    return [
        'id' => $faker->uuid,
        'description' => $faker->paragraph
    ];
});

LanguagesFactory

  $factory->define(\App\Models\Language::class, function (Faker $faker){
    $langCode = $faker->languageCode;
    return [
        'code' => $langCode,
        'name' => $langCode,
    ];
});

CategoriesFactory

    $factory->define(\App\Models\Category::class, function (Faker $faker){

    return [
        'main' => $faker->text,
        'sub' => $faker->text,
    ];
});

Can i get some help with this please i've hit a brick wall some help and examples would be great :) thanks!


Solution

  • You are very much on the right track. There are, however, some syntax errors.

    This is how you grab a film, eager load required relationships and return it as JSON:

    public function show($id)
    {
        $film = Film::with(['languages', 'categories'])->findOrFail($id);
    
        return $film; // will automatically convert it to JSON
    }
    

    And this is how you test it:

    public function testShowFilm()
    {
        $film = factory(Film::class)->create();
    
        $language = factory(Language::class)->create();
    
        $film->languages()->attach($language->id); // associate the film with the language
    
        $category = factory(Category::class)->create();
    
        $film->categories()->attach($category->id); // associate the film with the category
    
        $url = '/films/' . $film->id;
    
        $this->json('GET', $url)
            ->assertStatus(200)
            ->assertJsonPath(['id' => $film->id])
            ->assertJsonPath(['description' => $film->description])
            ->assertJsonPath(['languages.0.id' => $language->id])
            ->assertJsonPath(['categories.0.id' => $category->id]);
    }
    

    Note that in your scenario you should have:

    • 3 model tables: films, categories, languages;
    • a factory for each model table;
    • 2 relationship tables: film_categories and film_languages.

    Then you can either set relationships as part of your test using the attach method as in the example above, or you can use factory callbacks:

    https://laravel.com/docs/6.x/database-testing#factory-callbacks