3

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' => '[email protected]',
            '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' => '[email protected]',
            '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',
                    ]
        ]);
    }
}
8
  • As Abdulla already mentioned, one is an array of books, the other is a single book, so you must do the distinction (one has * the other does not) Commented May 29 at 11:56
  • issue was resolved and updated code index and show test is indeed succeed... but after multiple test there is like 30% chance one of them or both failed and got status code 500 not 200... is it normal or not in testing? Commented May 29 at 14:12
  • At first testing, both of them succeed... but after like 10 test... only index fails at test 11... only show fails at test 12... rarely, both of them also fails. Commented May 29 at 14:22
  • That is not normal, there is something wrong on your test (how you set up the test) or on the code itself and the test is literally finding something to fix. The only way is to know what the error is, add ->dump() after ->getJson(), so when it fails, you can see the data sent back to you Commented May 29 at 16:29
  • Index fails show succeed got me "message": "Attempt to read property "name" on null" and error exception on index but fine in show. Both Index and Show succeed got data as I expected too. when show fails also get "message": "Attempt to read property "name" on null" and "exception": "ErrorException" Commented May 30 at 10:33

1 Answer 1

3

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}");
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for answering, the problems get resolved after changing code for show test into single book object and routing using books id... but sometime index or show test failed because status code 500(index) or status code 404(show)... is create using factory sometimes caused failure or what?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.