DEV Community

Simon Bundgaard-Egeberg for IT Minds

Posted on • Originally published at insights.it-minds.dk

Develop Laravel with TDD

Test-driven development helps a developer set up predicates that the code should solve, before writing the actual code.

After the code is written, the test should pass, and the code now supports that predicate.

Sometimes using a purely TDD approach the development time will slow down a lot. And many "buyers" of software solutions do not want to pay this cost upfront.

Laravel provides the tools to create feature tests. These tests fill in the role of integration tests.

Before we get too much into a specific case, it is also worth noting that Laravel gives you an easy way to mock out your database with an in-memory one, which really gives you confidence in the tests you write, since the Feature tests test the data model and its write/read functionality.

The only thing that needs to be added to make sure that you can use this functionality is these lines in the phpunit.xml file.

  <php>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="DB_DATABASE" value=":memory:"/>
  </php>

To make sure that each test has its own pristine version of the database to work with, you can add a use statement in the class, which in turn makes sure that the database is wiped between tests.

class ProjectControllerTest extends TestCase
{
  use RefreshDatabase;
  // ....

}

Say for instance we want to make an endpoint that exposes some projects that a user owns. A test for such a feature (before writing the actual controller to expose it) would look something like this:

  /** @test */
  public function can_return_all_user_projects()
  {
    // Assign
    $this->user_under_test->projects()->saveMany(factory(Project::class, 5)->make());

    // Act
    $res = $this->getJson("api/projects", $this->headers());

    // Assert
    $res_json = $res->json();
    $expected_res = $this->user_under_test->projects()->count();
    $this->assertEquals(count($res_json), $expected_res);
  }

This test firstly creates 5 projects to the user under test.
Then it calls the endpoint api/projects which I then expect to return the same amount of projects that I created for the user in the first place.

To start with, this test would fail, since we have no endpoint that returns projects. So let us go ahead and create that.

// ProjectController.php
  public function index()
  {
    $userId = Auth::user()->id;
    $userProjects = Project::where("user_id", $userId)->get();

    return response($userProjects, 200);
  }

First of all, we make sure that we have the authenticated user, and we fetch all of the projects that match the userId from that user.

and boom our test now passes.

Now we get the requirement that all projects can have a scratchpad associated with it.

From here we go back and write a new test that could make sure that if there is a scratch on the project, this scratch is also returned with the project.

such a test might look like this:


  /** @test */
  public function if_present_should_return_scratches_on_get_all()
  {
    // Assign
    $new_project = $this->user_under_test->projects()->save(factory(Project::class)->make());
    $user_details = $this->user_under_test->user_details;

    $new_project->scratchpads()
      ->save(factory(Scratchpad::class)
        ->make(["user_details_id" => $this->user_under_test->user_details->id]));
    // Act
    $res = $this->getJson("api/projects", $this->headers());

    // Assert
    $res_json = $res->json();
    $exptected_res = $user_details->refresh()->scratchpads->toArray();
    $this->assertEquals($res_json[0]["scratchpads"], $exptected_res);
  }

The test above says that if there is a user with a project, and that project has a connected scratchpad, I expect that scratchpad to match the one returned from the endpoint.

Very well, but this test fails at this point, since there is no scratches
present on the payload.
A quick fix to the index function solves this:

  public function index()
  {
    $userId = Auth::user()->id;
    $userProjects = Project::with(["scratchpads"])->where("user_id", $userId)->get();

    return response($userProjects, 200);
  }

And boom, our test is again green.

This was not meant to lecture in the use of TDD, but merely how Laravel provides tools that make this process great and fluent.

As a final note, I want to say that if a feature test has tested anything else than the response from the endpoint, that test should maybe be moved out to a unit test. The Feature tests are meant to test the overall functionality of the feature.

It is specifically for this reason I think that the Laravel feature tests are powerfull. It creates a fine line where there is a lot of value given, for not to much effort.

You can read more about testing in Laravel here (https://laravel.com/docs/7.x/testing)[https://laravel.com/docs/7.x/testing].

Top comments (0)