I usually do only database assertions cause I find them cleaner.
Writing Test Assertions for Feature vs Unit
Hey all I’m looking for assistance with knowing what assertions should be Feature/Unit tests.I'm following along with the current series Jeffrey Way is doing on Testing and trying to think out what assertions I need but I tend to favor more of assert more than whats needed so I'd like some thoughts.
/**
* @test
* @dataProvider administrators
*/
public function invoke_activates_an_unactivated_user_and_redirects($administrators)
{
$now = now();
Carbon::setTestNow($now);
$user = User::factory()->unactivated()->create();
$this->actAs($administrators)
->patch(route('users.activate', $user))
->assertRedirect(route('users.index'));
tap($user->fresh(), function ($user) use ($now) {
$this->assertEquals(UserStatus::ACTIVE, $user->status);
$this->assertCount(1, $user->activations);
$this->assertEquals($now->toDateTimeString(), $user->activations->first()->started_at->toDateTimeString());
});
}
<?php
namespace App\Http\Controllers\Users;
use App\Http\Controllers\Controller;
use App\Http\Requests\Users\ActivateRequest;
use App\Models\User;
class ActivateController extends Controller
{
public function __invoke(User $user, ActivateRequest $request)
{
$user->activate();
return redirect()->route('users.index');
}
}
<?php
namespace App\Models\Concerns;
use App\Exceptions\CannotBeActivatedException;
use App\Exceptions\CannotBeDeactivatedException;
use App\Models\Activation;
trait CanBeActivated
{
/**
* Activate a model.
*
* @param string|null $startedAt
* @return void
*/
public function activate($startedAt = null)
{
throw_unless($this->canBeActivated(), new CannotBeActivatedException('Entity cannot be employed. This entity is currently employed.'));
$startDate = $startedAt ?? now();
$this->activations()->updateOrCreate(['ended_at' => null], ['started_at' => $startDate]);
$this->updateStatusAndSave();
}
}
Sure. $this->assertEquals(UserStatus::ACTIVE, $user->status) is breaking encapsulation. You are showing internal object state to the outside world and let them make decisions on it. What I would do is follow tell don't ask principle for it. So I would do $this->assertTrue($user->hasActiveStatus()) or $this->assertTrue($user->isActive()).
Also I wouldn't do $this->assertCount(1, $user->activations), I would use something as example above $this->assertTrue($user->hasActivations()).
$this->assertEquals($now->toDateTimeString(), $user->activations->first()->started_at->toDateTimeString()); is also to much to understand and comparing internal state outside of the object. Apply same logic as shown in two examples above.
Bonus:
$now = now(); Carbon::setTestNow($now); can be Carbon::setTestNow($now = now());
['started_at' => $startDate] can be ['started_at' => $startedAt ?? now()]
Based on the snippet, code below produces too many queries:
$this->activations()->updateOrCreate(['ended_at' => null], ['started_at' => $startDate]);
$this->updateStatusAndSave();
You should do your database interactions to a minimum. If you have 100 operations on the object you will have 100 queries just for that single object. You should do the transformations on the object state and just at the perimeter of the business layer just persist the changed state.
Take a look at the LoD principle.
List goes on, but that should be enough to get you started.
Please or to participate in this conversation.