phplaravelunit-testingphpunithttptestingcontroller

Asserting Lists (i.e., Non-Associative Arrays) in Tests: PHPUnit and Laravel HTTP Tests Without Considering Item Order


As you know when in PHPUnit we use assertEquals() and assertSame() and we pass arrays to them, they will assert arrays based on key-value pairs. So if the array is a non-assoc array (list) and the order doesn't matter, the tests will fail, since these methods consider the index as a key and compare the value for each corresponding key. This problem can be easily fixed by a custom assertion method like this:

protected function assertListWithoutOrderEquals(array $expected, array $actual): void
{
    sort($expected);
    sort($actual);

    $this->assertEquals($expected, $actual);
}

But when we are in the Laravel HTTP test and we have JSON to assert, Laravel will convert JSON to an array and assert them based on these methods, I think. I don't have any idea how to fix this problem here.

For example, I have this test in Laravel and I have a problem with asserting genres value.:

use Illuminate\Testing\Fluent\AssertableJson;

public function test_http_response(): void
{
    $expectedData = [
        'id' => 1,
        'name' => 'The fantastic book',
        'genres' => ['science-fiction', 'drama', 'mystery'],
        // other elements
    ];

    $response = $this->get('url');

    $response->assertJson(
        fn(AssertableJson $json) => $json->where('id', $expectedData['id'])
            ->where('name', $expectedData['name'])
            ->where('genres', $expectedData['genres']) // This is order sensitive and makes tests to fail.
            ->etc()
    );
}

I tried this but it's messy. I'm looking for a better and cleaner solution if you can help me.

->where('genres', fn($genres) => $genres->diff($expectedData['genres'])->isEmpty() && $genres->count() == count($expectedData['genres']))

To explain better, Laravel will convert the JSON array to the collection, so I checked diff() and since diff() is a one-way method, I mean it checks that all items in the first array exist in the second array and don't consider extra items in the second array, I checked the size of them as well.


Solution

  • I think a callback will be necessary in this case but you can take advantage of the same logic that PHPUnit uses to implement assertEqualsCanonicalizing

    $expectedData = [
        'id' => 1,
        'name' => 'The fantastic book',
        'genres' => ['science-fiction', 'drama', 'mystery'],
        // other elements
    ];
    $result = $this->get('url');
    
    $result->assertJson(
       fn(AssertableJson $json) => $json->where('id', $expectedData['id'])
           ->where('name', $expectedData['name'])
           ->where('genres', fn ($val) => (new IsEqualCanonicalizing($val->all()))->evaluate($expectedData['genres']))
    );
    

    I'm not sure if this is the "best" way but it is "a" way.