Testing your applications

We want to cover as much of the code we write with tests. Since most of our products are developed using Laravel as a platform, we get much of our test setup for free.

Please review the Laravel documentation for more insight in the availiable test methods.

We use PHPUnit for testing. The PHPUnit version used will vary on per-product basis.

As a general rule we write feature-tests. If you write some code that does something extra, a unit test is much appreciated, but as a general rule we don't care as much as how you implemented the code as long as you can prove it works via tests.

Naming your tests

Tests should be namespaced under the current feature. If your feature is small (less than 3 tests) you may deviate from this.

If at some point your feature outgrows this rule, go back to namespacing the tests correctly.

  • Bad: tests/Feature/AircraftRepairTest.php
  • Good: tests/Feature/Aircraft/RepairTest.php

Test method naming conventions

Each test method should be descriptive and explain exactly what you are testing. Method names should use snake_case. Prefix your methods with a /** @test **/ doc block.

// Bad

public function testUploadDocument()
{
    $this->assertTrue(true);
}

// Good

/** @test **/
public function a_user_can_upload_a_document_to_their_profile()
{
    $this->assertTrue(true);
}

Writing your test

Your test methods should be slim, and non-repetitive. The exception to this is single-method test files. If you find yourself repeating the same code more than two times per test file, consider extracting helper methods.

This example demonstrates tests that repeat a block of code multiple times. This is bad.

/** @test **/
public function it_can_find_many_candidates_from_norway() 
{
    factory(Candidate::class, 20)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Norway']);
    });
    factory(Candidate::class, 30)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Sweden']);
    });
    factory(Candidate::class, 40)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Denmark']);
    });
    ...
    $this->assertCount(20, Candidate::whereNationality('Norway')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_sweden() 
{
    factory(Candidate::class, 20)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Norway']);
    });
    factory(Candidate::class, 30)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Sweden']);
    });
    factory(Candidate::class, 40)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Denmark']);
    });
    ...
    $this->assertCount(30, Candidate::whereNationality('Sweden')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_denmark() 
{
    factory(Candidate::class, 20)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Norway']);
    });
    factory(Candidate::class, 30)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Sweden']);
    });
    factory(Candidate::class, 40)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Denmark']);
    });
    ...
    $this->assertCount(40, Candidate::whereNationality('Denmark')->count());
}

It is much better to extract that specific block to a helper method on the class to reduce the visual clutter. Make sure your helper methods are named in such a way that they are descriptive enough to not need any explaination. This is good.

/** @test **/
public function it_can_find_many_candidates_from_norway() 
{
    $this->createCandidates();
    ...
    $this->assertCount(20, Candidate::whereNationality('Norway')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_sweden() 
{
    $this->createCandidates();
    ...
    $this->assertCount(30, Candidate::whereNationality('Sweden')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_denmark() 
{
    $this->createCandidates();
    ...
    $this->assertCount(40, Candidate::whereNationality('Denmark')->count());
}

public function createCandidates() 
{
    factory(Candidate::class, 20)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Norway']);
    });
    factory(Candidate::class, 30)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Sweden']);
    });
    factory(Candidate::class, 40)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Denmark']);
    });
}

Alternatively, if all your test in the file will use this you can also move the code to the setUp() method. This is also good.

public function setUp() 
{
    parent:setUp();

    factory(Candidate::class, 20)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Norway']);
    });
    factory(Candidate::class, 30)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Sweden']);
    });
    factory(Candidate::class, 40)->create()->each(function($candidate) {
        $candidate->nationalities()->create(['value' => 'Denmark']);
    });
}

/** @test **/
public function it_can_find_many_candidates_from_norway() 
{
    ...
    $this->assertCount(20, Candidate::whereNationality('Norway')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_sweden() 
{
    ...
    $this->assertCount(30, Candidate::whereNationality('Sweden')->count());
}

/** @test **/
public function it_can_find_many_candidates_from_denmark() 
{
    ...
    $this->assertCount(40, Candidate::whereNationality('Denmark')->count());
}