phpjsonlaraveltestingphpunit

Laravel API Testing index and show data fail at assertJsonStructure of Nested Json back-and- fort


I have API for books that I want to test using phpunit, but the structure of nested json somehow make index and show test function making one of them fail.

If I wrote this code for both index and show test, only index test succeed and show test error( PHPUnit\Framework\Assert::assertArrayHasKey(): Argument #2 ($array) must be of type ArrayAccess|array, int given)

$response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  => [
                '*' =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
            ]
        ]);

But if I wrote show test like this, index test fail(status code received:500, expected code:200) but show test succeed

$response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
        ]);

Does anyone know what causing this problem in testing code?

test/feature/api/bookcontrollertest.php(index succeed)

<?php

namespace Tests\Feature\API;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Book;
use App\Models\User;
use App\Models\Categories;
use Laravel\Sanctum\Sanctum;

class BookControllerTest extends TestCase
{
    use RefreshDatabase;
    public function setUp(): void
    {
        parent::setUp();
        $user = User::factory()->create([
            'email' => 'test@dev.com',
            'password' => bcrypt('password'),
        ]); 
        Sanctum::actingAs($user);
    }
    public function test_book_index(): void
    {
        $k=Categories::factory(6)->create();
        $books = Book::factory(2)->create();
        $response = $this->getJson('/api/books');
        $response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  => [
                '*' =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
            ]
        ]);
    }
    public function test_book_show(): void
    {
        $k=Categories::factory(6)->create();
        $books = Book::factory(2)->create();
        $response = $this->getJson('/api/books/1');
        
        $response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  => [
                '*' =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
            ]
        ]);
    }
}

so I tried delete jsonstructure wildcard(*) but it causing index to fail. I want both of them succeed. dd($response) in show test function got one array of data and in index test function got two array of data. So route was fine, just when both of them tested making the other one fail.

Update 1: test/feature/api/bookcontrollertest.php(both succeed)

<?php

namespace Tests\Feature\API;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Book;
use App\Models\User;
use App\Models\Categories;
use Laravel\Sanctum\Sanctum;

class BookControllerTest extends TestCase
{
    use RefreshDatabase;
    public function setUp(): void
    {
        parent::setUp();
        $user = User::factory()->create([
            'email' => 'test@dev.com',
            'password' => bcrypt('password'),
        ]); 
        Sanctum::actingAs($user);
    }
    public function test_book_index(): void
    {
        $k=Categories::factory(6)->create();
        $books = Book::factory(2)->create();
        $response = $this->getJson('/api/books');
        $response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  => [
                '*' =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
            ]
        ]);
    }
    public function test_book_show(): void
    {
        $k=Categories::factory(6)->create();
        $books = Book::factory()->create();
        $response = $this->getJson("/api/books/{$books->id}");
        $response->assertStatus(200)->assertJsonStructure([
            'success',
            'message',
            'data'  =>[
                        'id',
                        'book_code',
                        'book_title',
                        'author',
                        'category',
                        'publisher',
                        'stock',
                        'book_cover',
                        'book_desc',
                        'barcode',
                    ]
        ]);
    }
}

Solution

  • The issue isn’t Laravel or PHPUnit. It’s your test.

    # for array of books
    'data' => [
        '*' => [
            'id',
            'book_code',
            ....
        ]
    ]
    
    # single book object
    'data' => [
        'id',
        'book_code',
        ....
    ]
    

    FYI: You can use this without hard hardcoded ID for books

    $response = $this->getJson("/api/books/{$book->id}");