The Laravel Testing 101 ebook is launched and ready!

I am extremely excited to announce the availability of the Laravel testing 101 ebook!

I have spent countless hours working on this ebook over the last months and I hope that it will help you learn how to add tests to your Laravel CRUD applications.

You can get your copy for just 19.99$ (limited time only):

https://leanpub.com/laraveltesting101

 

you can also sign up here to get 3 free chapters:

https://laraveltesting101.com/

Please let me know any feedback by tweeting at me (@djug) or simply emailing me (my first name at gmail).

[Laravel Testing 101] Writing tests for guest user functionalities on a Laravel CRUD application

This is an excerpt of my upcoming ebook Laravel Testing 101. If you haven’t read the previous chapters yet (available for free here: Adding Tests to your Laravel CRUD Application: Where to Start? and here: What should we be testing in a [laravel] CRUD application?), please do so before reading this one.

Now that we have a better idea about the functionalities we should be testing in our Laravel application, let’s start with testing what a guest can do, since it is less complicated than what a logged in user can do.

As we discussed I the previous chapter, here are the functionalities related to guests that we have in the application:

  1. A guest could see all the articles when visiting /articles
  2. A guest could see a single article
  3. A guest could see a user profile
  4. A guest could not write a new article and get redirected to the signup page instead
  5. A guest could visit and get the signup page
  6. A guest could visit and get the login page

Make sure that PHPUnit is working properly with your application

Before we start writing any tests, let’s make sure that PHPUnit is working properly with your application.

The binary of PHPUnit is included in vendor/bin/phpunit in your project, so all you need to do is to execute it (from your project directory).

You should see this result:

Even though we haven’t written any tests yet, Laravel includes the following example tests:

  • /tests/Feature/ExampleTest.php
  • /tests/Unit/ExampleTest.php

P.S.: I recommend adding an alias for the command above, so you would not need to type vendor/bin/phpunit each time you want to run the tests

For example, I’m using this alias:

alias lphpunit="vendor/bin/phpunit"

1/ A guest could see all the articles when visiting /articles

Since we are going to test functionalities related to the ArticleController, let’s first create a class dedicated to this controller.

php artisan make:test ArticleControllerTest

Note that I didn’t pass the --unit flag to the command, which mean we are not creating a unit test but rather a feature test. The newly created class should be located in /tests/Feature/ArticleControllerTest.php

You can get rid of the testExample that was included with the ArticleControllerTest.

Let’s create our first tests.

When executing PHPUnit, it will look for all the public method that either start with test or that have @test in their dockblock.

So you could either use this format:

public function testGuestCouldSeeListOfArticles()
{
...
}

or this one:

/**
* @test
*/
public function it_allows_anyone_to_see_list_all_articles()
{
...
}

I prefer the second one since it is much easier to read.

I’ll start with the most basic test. I just want to make sure whenever I hit the /articles route, I get a valid page back.

/**
* @test
*/
public function it_allows_anyone_to_see_list_all_articles()
{
	$response = $this->get(route('get_all_articles'));
	$response->assertSuccessful();
}

Save the file and run PHPUnit (either using vendor/bin/phpunit or with the lphpunit alias we created earlier).

Our tests passed 🙂

Note that, even though we wrote just one test and one assertion PHPUnit tells us that we have 3 tests and 3 assertions.

Note: An assertion is testing one single “thing”, a test could
contain multiple assertions
. The test we wrote above contains just a
single assertion $response->assertSuccessful();

The reason behind this, is that PHPUnit will run all the tests in the /tests directory. Running all the tests each time is not a problem at the stage we are in right now, but if you want to run just a single test, you can pass the name of the test (the name of the method) as a parameter to the --filter flag like this:
lphpunit --filter=it_allows_anyone_to_see_list_all_articles

… and yes, as you might have guessed it you could add an alias for this command in order to save time next time you want to run just a single test.

alias lphpunit="vendor/bin/phpunit"  
alias lphpunitf="lphpunit --filter="

lphpunitf it_allows_anyone_to_see_list_all_articles

As you can tell, we want to test more than just getting a valid page, we want to make sure that we are getting the right page.

We can test this with the following steps:

  • make sure that we are getting the right view
  • make sure that the view contains the variables needed for this page

Laravel provides two method to test the above:

$response->assertViewIs('articles.index');
$response->assertViewHas('articles');

Our test class should now look like this:

<?php
namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ArticleControllerTest extends TestCase
{
	/**
	* @test
	*/
	public function it_allows_anyone_to_see_list_all_article()
	{
		$response = $this->get(route('get_all_articles'));

		$response->assertSuccessful();
		$response->assertViewIs('articles.index');
		$response->assertViewHas('articles');
	}

}

Now that we can test that we are getting the right view (with the right variable), we no longer need to keep the first assertion, since it is implicit.

2/ A guest could see a single article

Now that we tested that a guest user could see the list of all articles, let’s make sure that she could view individual articles as well.

In order to ensure that this functionality (showing individual articles to guest users) works as expected, we would need the following steps:

  1. Get an article to view (randomly)
  2. Generate the route to this article and send a GET request to it
  3. Make sure that we are getting the right view (in this case articles.view)
  4. Make sure that the view returned contains a variable named $article
  5. Make sure that we are getting the article we wanted to access and not another one.

Our test should look like this:

/**
* @test
*/
public function it_allows_anyone_to_see_individual_articles()
{
	$article = Article::get()->random();
	$response = $this->get(route('view_article', ['id' => $article->id]));
	
	$response->assertViewIs('articles.view');
	$response->assertViewHas('article');
	$returnedArticle = $response->original->article;
	$this->assertEquals($article->id, $returnedArticle->id, "The returned article is different from the one we requested");
}

Note:
We can access the returned view thought the $response->original variable

You might ask why we are doing all these steps for a such simple feature. The feature is indeed simple, and its tests are simple as well… simple, but not trivial.

We are doing all these steps to insure the following:

  • We want to test accessing a random article each time (we would not want to always request the same ID or the same article), because we might have an issue in the code that makes the application always return the same article. Imagine for instance, that instead of searching for a specific article, for some reason we updated our code to use Article::first(), we wouldn’t be able to detect this issue if we keep returning the same article (using the same ID) over and over again.
  • It is highly recommended to use routes in your tests instead of URLs, because if you ever change the structure of your URL, you wouldn’t need to update your tests.
  • We also want to make sure that we are indeed getting the article we are requesting, because we might get the right view that includes the variable we are looking for, but it might contain a different article from the one we requested.

3/ A guest could see a user profile

This one should look a lot like the previous test since the concept is the same (access a model and return it), but we are accessing a user instead of an article.
Since we are not testing articles here, we should create a new test class first:

php artisan make:test UserControllerTest

Then all what we need to do is to add the following test:

/**
 * @test
 */
public function it_allows_anyone_to_see_users_profiles()
{
    $user = User::get()->random();

    $response = $this->get(route('show_user_profile', ['id' => $user->id]));

    $response->assertViewIs('users.show');
    $response->assertViewHas('user');

    $returnedUser = $response->original->user;

    $this->assertEquals($user->id, $returnedUser->id, "The returned user is different from the one we requested");
}

4/ A guest could not write a new article and gets redirected to the signup page instead

This one (and the remaining ones in this chapter) are much simpler, since they do not require accessing the DB.

In order to test this functionality, we need the following steps:

  • attempt to access the create_new_article route
  • test whether we got redirected to the login page [login or signup page ?]

The test should look like this:

/**
* @test
*/
public function it_prevent_non_logged_in_users_from_creating_new_articles()
{
	$response = $this->get(route('create_new_article'));
	$response->assertRedirect('login');
}

5. A guest could visit and get the signup page and 6. A guest could visit and get the login page

These two tests are even simpler to write, since we are just checking that when we attempt to visit the login and signup page, we get valid pages. Since we are using the Laravel built-in authentication controller, we would not need to test the authentication ourselves.

We would need a new test class for these two tests as well. We could either create a dedicated class just for them. Usually I put all the “page tests” (i.e tests that ensure we are getting valid pages when we hit certain URL) in a PagesControllerTest (especially if I have a controller named PagesController) ; or just create a test class for HomeController since in most cases I add the logic of the pages I tests to this class.

The two test cases should look like this:

/**
* @test
*/
public function it_returns_register_page()
{
	$response = $this->get(route('register'));
	$response->assertSuccessful();
}
/**
* @test
*/
public function it_returns_login_page()
{
	$response = $this->get(route('login'));
	$response->assertSuccessful();
}

Also, instead of just checking whether we are getting a valid page (which is more than enough in this situation), we also check that we are getting the right views like this:

/**
* @test
*/
public function it_returns_register_page()
{
	$response = $this->get(route('register'));
	$response->assertViewIs('auth.register');
}
/**
* @test
*/
public function it_returns_login_page()
{
	$response = $this->get(route('login'));
	$response->assertViewIs('auth.login');
}

How would tests detect breaking changes in the code base?

As we discussed in a previous chapter, one goal for writing tests is to ensure that the functionalities of the application will keep working the way we intended when we first built them.

I’d like to show just one quick example of how the tests will inform us that we are introducing a new code that is changing the behavior of the application (a breaking change).

Let’s assume that after a few weeks of working on this application we decided for some reason to update the constructor of the ArticleController from this:

public function __construct()
{
	$this->middleware("can:manage,article")->only('edit', 'update', 'delete');
	$this->middleware("auth")->only('create');
}

to this:

public function __construct()
{
$this->middleware("can:manage,article")->only('edit', 'update', 'delete');
$this->middleware("auth");
}

the only change is deleting ->only('create') from the second line of the constructor.

This might either happen by accident (a teammate didn’t see the value of protecting just one action with this middleware) or it was done intentionally to prevent guest users from reading articles before signing in.

If we run the tests, we would get this:

Before writing any tests of the application, chances are that you wouldn’t notice the breaking change before a while. Maybe the breaking change will even get deployed without anyone noticing it, since you’d be using your application as a logged in user most of the time, and you wouldn’t think that you’d need to test the guest functionalities after every small change to the application.

But with the tests, any breaking change will be detected right away without even needing to test the application manually. And if you have a CI (Continuous Integration) set up with your project (we will be exploring how to set it up later in this ebook), you wouldn’t even be able to merge/deploy without fixing the issue first.

Conclusion

In this chapter, we explored the different steps needed to test guest users functionalities. We’ve seen that even though the tests are simple, they sometimes require extra steps to ensure that we are testing the right thing, and we are not missing some edge cases (especially when we introduce a change that might break the application). We’ve also seen how tests would detect breaking changes in the code base.

In the upcoming chapters, we will be exploring tests related to logged in users which could be a little more challenging than the tests we’ve seen so far.

If you want to follow along, and get notified of any progress with this ebook (new free chapters for instance), please sign up here: https://laraveltesting101.com/

What should we be testing in a [laravel] CRUD application?

This is an excerpt of my upcoming ebook Laravel Testing 101. If you haven’t read the previous chapter yet (available for free here: Adding Tests to your Laravel CRUD Application: Where to Start?), please do so before reading this one.

 

Now that you understand that you should be looking at tests from a different angle, and that we should test mainly controllers, you might start to ask, “… but what should I be testing exactly?”

In this chapter, we are going to answer this question and describe what we will be testing in the rest of the ebook.

But first, let’s get the demo application the book is built upon up and running.

Clone the following repo

https://github.com/djug/laraveltesting101

and run the application after you follow all the steps in the readme file.

After you fire up your server/application, you should get this front page:

So far, here is what you can do within the application:

  • click on either “login” or “register” to see the login and signup pages.
  • click on “all articles” and you’ll get to /articles, that contains all the articles generated by the seeder.

  • click on “write a new article” (/new-article) and you’ll get redirected to the signup page (since adding a new article requires signing up).
  • from the “/articles” page, you could either click on articles name (example /articles/15), or user names (/users/5) to view the articles or their authors profiles.

After signing up you’d be able to view the /new-article route (which was redirecting to the sign up page when we tried it earlier without signing in)

After saving your article, you can view it, and since it is your own article, you would see the “edit article” button at the end of the page (you won’t see it on articles that you do not own).

When clicking on this button, you get the edit form of the article, where you can edit and save the article or you could delete it.

When updating the article you’ll get redirected to it again, with a success message.

But if you delete it, you’ll get redirected to the “all articles” page with a different success message.

You won’t see an “edit” button if you visit articles that you didn’t create yourself, but if you try to access the edit page of an article you don’t own via the URL directly (ex: articles/16/edit), you’ll get a 403 page.

Let’s go back to the “Write a new article” page and try to add a new article with a short title and/or a short body (shorter than 10 characters).

You’ll get an error message. And the same thing happens when editing.

So even though the application is small, there are multiple things we need to test to ensure that the application is working as expected.

And here is the list:

  1. A guest could visit and get the signup page
  2. A guest could visit and get the login page
  3. A guest could see all the articles when visiting /articles
  4. A guest could see a user profile
  5. A guest could not write a new article and get redirected to the signup page instead
  6. A user (i.e. signed up user) could write an article
  7. A user could access the edit page of her own articles
  8. A user could save the changes of her own articles and get redirected to the article page after editing with a success message
  9. A user could delete her own articles and get redirected to the all articles page with a success message
  10. A user should not see the edit button on the article she doesn’t own
  11. A user could not edit articles she doesn’t own
  12. A user could not delete articles she doesn’t own
  13. When creating an article, the article cannot have short title or body
  14. When updating an article, the article cannot have short title or body

So if we manage to automate testing the above list, we could be assured that each time we add a new feature or change a code, the application will keep working as expected.

In the next chapter, we will start writing tests with functionalities related to guests users.

If you want to follow along, and get notified of any progress with this ebook (new free chapters for instance), please sign up here: https://laraveltesting101.com/

Adding Tests to your Laravel CRUD Application: Where to Start?

This is an excerpt from my upcoming ebook Laravel testing 101

Imagine you are working on a Laravel side-project or you just joined a team working on a Laravel application, and it currently doesn’t have a single test. You want to change that, and add tests to the application. If you find yourself in this situation, you might not even know where to start. “Should I unit test everything? Should I just test the most critical parts of the application?” You might even find yourself asking “Why do I need tests in the first place?

I am writing an ebook to answer all these questions for you, and this is the introductory chapter. My main goal with this ebook  is to try to answer these questions, and I will describe my process in writing tests for some of the Laravel applications I’m working on (mainly CRUD applications).

 

What you are going to read in this chapter and the following ones might not necessarily be the optimal way of testing or even the best practice, but rather just one possible way of doing testing in Laravel [CRUD] applications, the way I am approaching testing these days.

 

Why we are writing fewer tests than necessary

For me, testing is just like physical exercise. We all know that we should be exercising (or exercising more), but very few of us exercise on a regular basis, if at all.

Many people decide they’ll be exercising every single day as part of their new year resolution. They get a yearly subscription at their local gym to prove how serious they are, they might even hit the gym 5 days in a row the first week of the year. But after the first weekend, they start going less and less, and end up just giving up because their body aches so much and they don’t see any improvement yet (they worked out for a WHOLE week, they should at least notice a quick drop in their weights, no?). The other scenario is that they might feel overwhelmed with all the choices they find at the gym: Should I focus just on cardio? Maybe lift some weights? Or maybe I should do both? Maybe I’ll just focus on my biceps… A classic case of paralysis by analysis.

Almost the same thing happens with developers and testing.

With every new project, they decide they’ll unit test the sh*t out of it. They decide to test every single class and method (even the private ones), they would even buy some ebooks or online courses to learn how to implement a testing approach in their project.

They’ll start implementing TDD, for instance, in the first week of the project, but after the first weekend passes by, they’ll start thinking about giving up. Not only they don’t see any improvement in their development process or in the end results (they have been TDDing everything for a whole week for God’s sake), but also they’ll start writing tests for just a method or two on each class, and then start to question if this approach is the right one or if they should try something else entirely, and then, the inevitable happens, they drop everything.

In other words, testing is just like exercising, we tend to think that we should do it perfectly (exercising every single day), or we shouldn’t bother at all. When in reality, all what we need is some good practices, and a change in the way we view and approach testing. We have to understand why we need tests in the first place, and learn how we can get the most out of them without doing too much.

 

Why do we need tests in the first place?

If you ask this question to a group of developers, you might not get the same answer twice. Personally, I write tests for one simple reason: to make sure that the application will keep working in the exact same way it was intended to when each piece of it was written. In other words, each time I introduce a new change to the code base or add new feature, I want to make sure that I’m not breaking any current functionalities.

If you think about it, each time you introduce a change and you start testing your app in the browser, you are just checking that everything is still working.  The method I’m describing here will just help you automate this whole process, i.e instead of opening random pages in the app to see whether they are still working, you execute your test suite, and confirm that everything is still working as expected.

Let me repeat it one more time, since this is really important, and all what follows is built upon this idea: write tests to make sure that the current functionalities keep working as expected when we introduce changes

In other words, testing should not be about proving that the code is 100% correct, all we need is to make sure that we are not going to break it or change its behavior in the future.

So, what should I be testing exactly?

As you can probably tell from the description above, we should be testing only controllers in this phase. If you think about it, if all the controllers are behaving exactly how they are supposed to, it is most likely that all the underlying code of the application (models, repositories, services…) is working just fine, unless your test suite doesn’t cover all the use cases of the controllers.

Structure of the ebook

What I don’t like in the majority of online resource about testing, is that they present each idea/concept in isolation. When usually, especially for a beginner, it is hard to tell how all these concepts fit together.

So instead of doing this, I’ll be adding tests to a small demo application I built for the purpose of this ebook.

It is a small application that allows users to sign up, and post articles. Each user can edit and delete their own articles, and read what other users post on the website.

The application is simple, but as you’ll see in the upcoming chapters, it contains enough code/functionalities to demonstrate the all the necessary ideas you would need in testing almost any Laravel CRUD application.

If you are interested, and you want to follow along, please visit laraveltesting101.com and sign up. You’ll be notified every time I publish something new.

 

laraveltesting101.com