Extra tools for your laravel tests
You can install the package via composer:
composer require soyhuce/laravel-testing --dev
To use Laravel specific assertions, you will have to add \Soyhuce\Testing\Assertions\LaravelAssertions::class
trait to your test class.
Matches if the model is equal to the given model.
/** @test */
public function myTest()
{
$user1 = User::factory()->createOne();
$user2 = User::find($user1->id);
$this->assertIsModel($user1, $user2);
}
Matches if the collections are equal.
$collection1 = new Collection(['1', '2', '3']);
$collection2 = new Collection(['1', '2', '3']);
$this->assertCollectionEquals($collection1, $collection2);
2 Collections are considered equal if they contain the same elements, indexed by the same keys and in the same order.
$this->assertCollectionEquals(new Collection([1, 2]), new Collection([1, 2, 3])); // fail
$this->assertCollectionEquals(new Collection([1, 2, 3]), new Collection([1, 2])); // fail
$this->assertCollectionEquals(new Collection([1, 2, 3]), new Collection([3, 1, 2])); // fail
$this->assertCollectionEquals(new Collection([1, 2, 3]), new Collection([3, 1, 2])); // fail
$this->assertCollectionEquals(new Collection([1, 2, 3]), new Collection([1, 2, "3"])); // fail
$this->assertCollectionEquals(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2])); // fail
$this->assertCollectionEquals(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2, 'c' => 4])); // fail
$this->assertCollectionEquals(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2, 'd' => 3])); // fail
$this->assertCollectionEquals(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'c' => 3, 'b' => 2])); // fail
$this->assertCollectionEquals(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2, 3])); // fail
If the Collections contain Models, assertCollectionEquals
will use Model comparison of assertIsModel
.
$user1 = User::factory()->createOne();
$user2 = User::find($user1->id);
$this->assertCollectionEquals(collect([$user1]), collect([$user2])); // Success
You can give an array in the $expected
parameter of assertCollectionEquals
:
/** @test */
public function theUsersAreOrdered(): void
{
$user1 = User::factory()->createOne();
$user2 = User::factory()->createOne();
$this->assertCollectionEquals(
[$user1, $user2],
User::query()->orderByDesc('id')->get()
);
}
Matches if the collections are canonically equals.
$collection1 = new Collection(['1', '2', '3']);
$collection2 = new Collection(['3', '2', '1']);
$this->assertCollectionEqualsCanonicalizing($collection1, $collection2);
2 Collections are considered equal if they contain the same elements, indexed by the same keys.
$this->assertCollectionEqualsCanonicalizing(new Collection([1, 2]), new Collection([1, 2, 3])); // fail
$this->assertCollectionEqualsCanonicalizing(new Collection([1, 2, 3]), new Collection([1, 2])); // fail
$this->assertCollectionEqualsCanonicalizing(new Collection([1, 2, 3]), new Collection([1, 2, "3"])); // fail
$this->assertCollectionEqualsCanonicalizing(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2])); // fail
$this->assertCollectionEqualsCanonicalizing(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2, 'c' => 4])); // fail
$this->assertCollectionEqualsCanonicalizing(new Collection(['a' => 1, 'b' => 2, 'c' => 3]), new Collection(['a' => 1, 'b' => 2, 3])); // fail
If the Collections contain Models, assertCollectionEqualsCanonicalizing
will use Model comparison of assertIsModel
.
$user1 = User::factory()->createOne();
$user2 = User::find($user1->id);
$this->assertCollectionEqualsCanonicalizing(collect([$user1]), collect([$user2])); // Success
You can give an array in the $expected
parameter of assertCollectionEqualsCanonicalizing
:
/** @test */
public function theUsersAreOrdered(): void
{
$user1 = User::factory()->createOne();
$user2 = User::factory()->createOne();
$this->assertCollectionEqualsCanonicalizing(
[$user1, $user2],
User::query()->get()
);
}
All these methods are available in Illuminate\Testing\TestResponse
:
Requires hotmeteor/spectator package
TestResponse::assertValidContract(int $status)
: Verifies that the request and the response are valid according to the contract.
TestResponse::assertData($expect)
: Alias forassertJsonPath('data', $expect)
TestResponse::assertDataPath(string $path, $expect)
: Alias forassertJsonPath('data.'.$path, $expect)
TestResponse::assertDataPaths(array $expectations)
: RunsassertDataPath
for each$path
=>$expect
pair in the array.TestResponse::assertDataPathCanonicalizing(string $path, array $expect)
: Alias forassertJsonPathCanonicalizing('data.'.$path, $expect)
TestResponse::assertDataPathsCanonicalizing(array<array> $expectations)
: RunsassertDataPathCanonicalizing
for each$path
=>$expect
pair in the array.TestResponse::assertDataMissing($item)
: Alias forassertJsonMissingPath('data', $item)
TestResponse::assertDataPathMissing(string $path, $item)
: Alias forassertJsonMissingPath('data.'.$path, $item)
TestResponse::assertJsonPathMissing(string $path, $item)
: Verifies that the Json path does not contain$item
TestResponse::assertJsonMessage(string $message)
: Alias forassertJsonPath('message', $message)
TestResponse::assertSimplePaginated()
: Verifies that the response is a simple paginated response.TestResponse::assertPaginated()
: Verifies that the response is a paginated response.
TestResponse::assertViewHasNull(string $key)
: Verifies that the key is present in the view but is null.
It's possible to test FormRequests in isolation thanks to the TestsFormRequests
trait.
$testFormRequest = $this->createRequest(CreateUserRequest::class);
$testFormRequest
have some methods to check authorization and validation of the request.
TestFormRequest::by(Authenticable $user, ?string $guard = null)
: set authenticated user in the requestTestFormRequest::withParams(array $params)
: set route parametersTestFormRequest::withParam(string $param, mixed $value)
: set a route parameterTestFormRequest::validate(array $data): TestValidationResult
: get Validation resultTestFormRequest::assertAuthorized()
: assert that the request is authorizedTestFormRequest::assertUnauthorized()
: assert that the request is unauthorizedTestValidationResult::assertPasses()
: assert that the validation passesTestValidationResult::assertFails(array $errors = [])
: assert that the validation failsTestValidationResult::assertValidated(array $expected)
: assert that the attributes and values that passed validation are the expected ones
For exemple :
$this->createRequest(CreateUserRequest::class)
->validate([
'name' => 'John Doe',
'email' => '[email protected]',
])
->assertPasses();
$this->createRequest(CreateUserRequest::class)
->validate([
'name' => null,
'email' => 12,
])
//->assertFails() We can check that the validation fails without defining the fields nor error messages
->assertFails([
'name' => 'The name field is required.',
'email' => [
'The email must be a string.',
'The email must be a valid email address.',
]
]);
$this->createRequest(CreateUserRequest::class)
->by($admin)
->assertAuthorized();
$this->createRequest(CreateUserRequest::class)
->by($user)
->assertUnauthorized();
$this->createRequest(UpdateUserRequest::class)
->withArg('user', $user)
->validate([
'email' => '[email protected]'
])
->assertPasses();
It's possible to test the JsonResources
in isolation thanks to the TestsJsonResources
trait.
TestsJsonResources::createResponse(JsonResource $resource, ?Request $request = null)
returns a Illuminate\Testing\TestResponse
.
$this->createResponse(UserResource::make($user))
->assertData([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
]);
Let's take this test
$user = User::factory()->createOne();
$this->mock(DeleteUser::class)
->shouldReceive('execute')
->withArgs(function(User $executed) use ($user) {
$this->assertIsModel($user, $executed);
return true;
})
->once();
// run some code wich will execute the mock
We can simplify this test by using a Matcher
.
$this->mock(DeleteUser::class)
->shouldReceive('execute')
->withArgs(Matcher::isModel($user))
->once();
For Collections, we can use Matcher::collectionEquals()
or Matcher::collectionEqualsCanonicalizing()
.
For more complex cases, we can use Matcher::make
.
$user = User::factory()->createOne();
$roles = Role::factory(2)->create();
$this->mock(UpdateUser::class)
->shouldReceive('execute')
->withArgs(function(User $executed, string $email, Collection $executedRoles) use ($user, $roles) {
$this->assertIsModel($user, $executed);
$this->assertSame('[email protected]', $email);
$this->assertCollectionEquals($roles, $executedRoles);
return true;
})
->once();
// Refactored to
$this->mock(UpdateUser::class)
->shouldReceive('execute')
->withArgs(Matcher::make(
$user,
'[email protected]',
$roles
))
->once();
In some cases, we wish to check only a few arguments or call argument methods:
$this->mock(CreateUser::class)
->shouldReceive('execute')
->withArgs(function(UserDTO $data, Collection $executedRoles) use ($team, $roles) {
$this->assertSame('[email protected]', $data->email);
$this->assertSame('password', $data->password);
$this->assertIsModel($team, $data->team())
$this->assertCollectionEquals($roles, $executedRoles);
return true;
})
->once();
We can use Matcher::match
to define our assertions on $data
:
$this->mock(CreateUser::class)
->shouldReceive('execute')
->withArgs(Matcher::make(
Matcher::match('[email protected]', fn(UserDTO $data) => $data->email)
->match('password', fn(UserDTO $data) => $data->password)
->match($team, fn(UserDTO $data) => $data->team()),
$roles
))
->once();
In specific cases of object properties, we can use named parameters:
$this->mock(CreateUser::class)
->shouldReceive('execute')
->withArgs(Matcher::make(
Matcher::match(email: '[email protected]', password: 'password')->match($team, fn(UserDTO $data) => $data->team()),
$roles
))
->once();
We can also check object type:
$this->mock(CreateUser::class)
->shouldReceive('execute')
->withArgs(Matcher::make(
Matcher::of(UserDTO::class)->properties(email: '[email protected]', password: 'password'),
$roles
))
->once();
The trait MocksActions
provides a mockAction
method to simply mock an action. By convention, an action is a class with a execute
method.
Under the hood, it uses Mockery::mock
.
It allows to easily define your action's expectations. Instead of
$user = User::factory()->createOne();
$this->mock(DeleteUser::class)
->shouldReceive('execute')
->withArgs(function(User $executed) use ($user) {
$this->assertIsModel($user, $executed);
return true;
})
->once();
you can write
$user = User::factory()->createOne();
$this->mockAction(DeleteUser::class)
->with($user);
You can also define the return value and capture it to use it in your test.
$this->mockAction(CreateUser::class)
->with(new UserData(email: '[email protected]', password: 'password'))
->returns(fn() => User::factory()->createOne())
->in($user);
$this->postJson('register', ['email' => '[email protected]', 'password' => 'password'])
->assertCreated()
->assertJson([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
]);
It can be necessary to capture the return value of a callback, for exemple in returnUsing
of mocks.
$this->mock(CreateOrUpdateVersion::class)
->expects('execute')
->andReturnUsing(
fn () => Version::factory()->for($package)->createOne()
)
->once();
// I need created Version ! How do I do ?
In this case, we will use capture
function:
$this->mock(CreateOrUpdateVersion::class)
->expects('execute')
->andReturnUsing(capture(
$version,
fn () => Version::factory()->for($package)->createOne()
))
->once();
Once the mock executed, $version
is created and will contain the returned value of the callback.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
- Colin DeCarlo for the FormRequest isolation testing
- Bastien Philippe
- All Contributors
The MIT License (MIT). Please see License File for more information.