Setting up unit tests (failed on github actions but good on local)
Hello,
To improve my knowledge, I'm developing a CRM with Laravel v11 and FilamentPHP v3.3. For this app, I'm trying to set up unit tests with Pest (which is new for me...). The aim is for the tests to run when I push a commit to GitHub via Github actions.
I've read several tutorials for writing tests and after running a few of them, I can see that they run successfully locally, without returning any errors:

However, when I go via GitHub (or locally via Act), some tests fail :
PASS Tests\DebugTest
✓ it will not use debugging functions 4.37s
FAIL Tests\Feature\ContactsTest
✓ it can list contacts 2.49s
✓ it can search contact by name 1.85s
✓ it can sort contacts by name 2.81s
✓ it can filter contacts by client 1.95s
⨯ it can create contact 1.64s
⨯ it can validates contact creation form 1.52s
⨯ it rejects invalid phone numbers for outside FR, LU, BE 1.54s
✓ it can retrieve contact data 1.38s
⨯ it can update contact details 1.57s
✓ it can delete a contact 1.43s
FAIL Tests\Feature\CustomersTest
✓ it can list customers 1.91s
✓ it can search customer by name 1.98s
✓ it can sort customers by name 3.14s
⨯ it can create customer 1.92s
✓ it can validates customer creation form 1.82s
✓ it rejects invalid phone numbers for outside FR, LU, BE 1.82s
✓ it can retrieve customer data 1.48s
⨯ it can update customer details 1.95s
✓ it can soft delete a customer 1.65s
PASS Tests\Feature\DashboardTest
✓ authenticated user can access to the CRM 1.85s
PASS Tests\Feature\GitHubServiceTest
✓ it can retrieve the latest GitHub tag 1.14s
FAIL Tests\Feature\UsersTest
✓ authenticated user can access to the users page and see the users l… 1.59s
✓ it allows login if email is verified and denies if not 2.14s
⨯ it can create user 1.47s
✓ it can validates user creation form 1.50s
✓ it can retrieve user data 1.32s
⨯ it can update user details 1.50s
✓ it can delete a user 1.41s
────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────
FAILED Tests\Feature\CustomersTest > it can update customer details
Failed asserting that a row in the table [customers] matches the attributes {
"name": "Updated Name",
"contact_mail": "[email protected]"
}.
Found: [
{
"name": "Philippe Potier",
"contact_mail": "[email protected]"
}
].
at tests/Feature/CustomersTest.php:123
119▕ ->fillForm($updatedData)
120▕ ->call('save')
121▕ ->assertHasNoFormErrors();
122▕
➜ 123▕ $this->assertDatabaseHas('customers', $updatedData);
124▕ });
125▕
126▕ it('can soft delete a customer', function () {
127▕ $customer = Customer::factory()->create();
────────────────────────────────────────────────────────────────────────────
FAILED Tests\Feature\UsersTest > it can create user
Component has errors: "data.name", "data.email", "data.password"
Failed asserting that false is true.
at vendor/livewire/livewire/src/Features/SupportValidation/TestsValidation.php:109
105▕ {
106▕ $errors = $this->errors();
107▕
108▕ if (empty($keys)) {
➜ 109▕ PHPUnit::assertTrue($errors->isEmpty(), 'Component has errors: "'.implode('", "', $errors->keys()).'"');
110▕
111▕ return $this;
112▕ }
113▕
+4 vendor frames
5 tests/Feature/UsersTest.php:54
────────────────────────────────────────────────────────────────────────────
FAILED Tests\Feature\UsersTest > it can update user details
Failed asserting that a row in the table [users] matches the attributes {
"name": "Updated Name",
"email": "[email protected]"
}.
Found: [
{
"name": "User 1",
"email": "[email protected]"
},
{
"name": "User 2",
"email": "[email protected]"
},
{
"name": "User 3",
"email": "[email protected]"
}
] and 2 others.
at tests/Feature/UsersTest.php:100
96▕ ->fillForm($updatedData)
97▕ ->call('save')
98▕ ->assertHasNoFormErrors();
99▕
➜ 100▕ $this->assertDatabaseHas('users', $updatedData);
101▕ });
102▕
103▕ it('can delete a user', function () {
104▕ $user = createUser();
Tests: 8 failed, 21 passed (154 assertions)
Duration: 54.32s
Error: Process completed with exit code 1.
The tests fail at the Features level on the same functions. Knowing that I use the same logic in my functions, I wonder if I've done it right... Here's my Feature/UsersTest for example:
<?php
use App\Filament\Resources\UserResource\Pages\CreateUser;
use App\Filament\Resources\UserResource\Pages\EditUser;
use App\Filament\Resources\UserResource\Pages\ListUsers;
use App\Models\User;
use Filament\Actions\DeleteAction;
use function Pest\Livewire\livewire;
function createUser()
{
return User::factory()->create();
}
test('authenticated user can access to the users page and see the users list', function () {
$users = User::all();
livewire(ListUsers::class)->assertCanSeeTableRecords($users);
});
it('allows login if email is verified and denies if not', function () {
$users = [
'verified' => User::factory()->create([
'email' => '[email protected]',
'email_verified_at' => now(),
'password' => bcrypt('password'),
]),
'unverified' => User::factory()->create([
'email_verified_at' => null,
'password' => bcrypt('password'),
]),
];
$this->actingAs($users['verified'])
->get(route('filament.crm.pages.dashboard'))
->assertStatus(200);
$this->actingAs($users['unverified'])
->get(route('filament.crm.pages.dashboard'))
->assertStatus(403);
});
it('can create user', function () {
$newUser = User::factory()->make();
livewire(CreateUser::class)
->fillForm([
'name' => $newUser->name,
'email' => $newUser->email,
'password' => $newUser->password,
'remember_token' => $newUser->remember_token,
])
->call('create')
->assertHasNoFormErrors();
$this->assertDatabaseHas(User::class, [
'name' => $newUser->name,
'email' => $newUser->email,
'remember_token' => $newUser->remember_token,
]);
expect($newUser->refresh())
->name->toBe($newUser->name)
->email->toBe($newUser->email);
});
it('can validates user creation form', function () {
livewire(CreateUser::class)
->fillForm([
'name' => 'Invalid User',
'email' => 'not-a-real-email',
'password' => 'password123',
])
->call('create')
->assertHasFormErrors(['email']);
});
it('can retrieve user data', function () {
$user = createUser();
livewire(EditUser::class, ['record' => $user->getRouteKey()])
->assertFormSet([
'name' => $user->name,
'email' => $user->email,
]);;
});
it('can update user details', function () {
$user = createUser();
$updatedData = [
'name' => 'Updated Name',
'email' => '[email protected]',
];
livewire(EditUser::class, ['record' => $user->getRouteKey()])
->fillForm($updatedData)
->call('save')
->assertHasNoFormErrors();
$this->assertDatabaseHas('users', $updatedData);
});
it('can delete a user', function () {
$user = createUser();
$this->assertDatabaseHas('users', [
'id' => $user->id,
]);
livewire(EditUser::class, ['record' => $user->getRouteKey()])
->callAction(DeleteAction::class)
->assertHasNoErrors();
$this->assertDatabaseMissing($user);
});
Do you see anything strange or wrong? Thanks for your help :)
Please or to participate in this conversation.