Interested how Laravel works under the hood? → Check my Laravel Core Adventures Course

3 Things You Need For Test-Driven Development

#testing #refactoring

Testing and Test-Driven Development (TDD) are essential and challenging topics in programming. Especially the switch from just writing some tests to TDD can be tricky.

After using TDD for several years, I see three things you need to know to make it work.

1. You Need To Know What To Test

This may sound pretty straightforward to some of you, but when you start testing, this is the biggest obstacle. Without experience, this is super hard. You also read the complicated terms like unit tests, feature tests, integration tests, etc. Then you see complex examples of all types of tests, and you feel even more overwhelmed.

That's why I always recommend starting with a very simple test. A test everybody understands immediately, but more importantly, can use right away.

public function it_gives_successful_response_for_homepage()
{
    $this->get('/')
        ->assertSuccessful();
}

In this test, we make a request to the homepage and check if the response is successful. You maybe need to explain that this assertion checks if the response code is between 200 and 300, but that's it. From now on, you know that your homepage is not throwing any errors. This is already a great success, and if you only have this one test, it still makes a positive impact.

From now on, you can test all the pages of your application, which I do in every project. Similarly, you will learn what else you can test on the way.

In the following example, I test a custom factory class of mine. I want to ensure that the output of the create method fits my expectations.

public function it_creates_new_post_file()
{
    Storage::fake('posts');

    $postPath = PostFactory::new()
        ->create();

    $this->assertFileExists($postPath);
}

So next to HTTP tests, where we check the response or output of an HTTP request, it is a good idea to test custom classes you create, like my PostFactory. Every public method indicates a new test you can write to ensure its behavior.

Another example of a custom class is a Laravel command. Here I want to check what it does when called with Artisan.

public function it_caches_posts_when_command_run()
{
    $this->assertFalse(Cache::has('posts'));

    $this->artisan(CachePostsCommand::class);

    $this->assertTrue(Cache::has('posts'));
 
}
Note: All provided examples are real tests from my applications.

The more you learn about testing, the bigger your list of things to test will grow. Testing also provides you more confidence when deploying your application or changing it. Therefore it can vary how many tests you will need to achieve that. This is your choice to make.

2. You Need To Know Your Stack

With stack, I don't refer to your low-level stack like local environment or coding language. I mean, how you build your PHP applications.

Mostly, this will be about your framework of choice and the tools you use to build your apps. For me, these tools are Laravel and Laravel Livewire. This is important because how I write my tests will depend on those tools.

You always start with a test with TDD. But it will be hard to write a test when you don't know how you will build your apps. That's why it is crucial to know your tools.

public function it_includes_video_player_component () {
    // Arrange
    $user = User::factory()
        ->withPurchasedProduct();

    // Act & Assert
    $this->actingAs($user)
        ->get(route('videos', $user->products->first()))
        ->assertSeeLivewire(VideoPlayer::class);
});

In the test above, I ensure a specific site includes a video player. Then, of course, I could check if the site contains the player's headline, the current video name, previous and next buttons, etc. But since this is a Livewire component, I like to test the component on its own.

public function it_orders_player_videos() {
    // Arrange
    createProductVideos(3, new Sequence(
        ['title' => 'Outro', 'order_slot' => '3'],
        ['title' => 'Intro', 'order_slot' => '1'],
        ['title' => 'Middle', 'order_slot' => '2']
    ));

    // Act & Assert
    Livewire::test(VideoPlayer::class, ['productId' => Product::first()->id])
        ->assertSeeInOrder(['Intro', 'Middle', 'Outro']);
});
Note: Laravel Livewire has its own testing helper which I use to test Livewire components.

If I didn't test this component in isolation, I would have to test the same things on every page I use this player.

That's why I only ensure the component is used on the video page. Everything else will be tested in a separate component test.

When I start a new project, these are all things I already know, and they help a lot while writing tests before the implementation.

3. You Need To Change Your Workflow

With TDD, you always write a test first. Of course, this approach has variations, but we focus on the original concept today.

Writing a test before you know the implementation is a significant shift in thinking; it will take a while before you get used to it. The new workflow is also often referred to as Red Green Refactor, and you probably have already heard of it. It describes the three essential stages of the workflow.

Red 🛑

Red stands for a failing test. This is also true for a new, empty test, and that's where we start.

It is often a good idea to think about what you want to test first and write an empty function for each of them.

public function it_shows_posts_per_page()
{
}

public function it_shows_post_prev_and_next_buttons()
{
}

public function it_resets_pagination_after_search()
{
}

Green ✅

The next step is making your test Green, which means passing it. You must now implement your new feature, and the test will tell you if it works as expected.

This is a crucial step because there are many ways to make your test green. But we are only interested in the fastest one. We don't care if the code is beautiful or SOLID or whatever. We want it to work, which is the most important thing.

Refactor ♺

Only after a test passes can we start thinking about how to improve the code. This could mean anything from code-styling to making it more readable or simple.

Because we already know that our first solution worked, we will immediately see if the new solution is failing. This will give you a lot of confidence for refactoring your code to your needs.

Conclusion

Test-Driven Development has become my default for coding for some years now. It helps me to focus better on what I really want to develop, without getting lost in details or other todos. Having tests also lets me sleep better at night when knowing my code works.

Do you enjoy my posts?

Sign up for my newsletter to receive updates on my latest content.

You will receive monthly updates on my latest articles and products. I do care about the protection of your data. Read my Privacy Policy.