Laravel comes shipped with a robust testing suite that allows developers to perform unit tests, test API endpoints, and run automated feature tests in the browser.

In this post, we will be adding various types of testing processes that are available in the Laravel framework to our CircleCI configuration file in order to automate these tests each time we push new code.

Prerequisites

To follow along with this post, there are a couple of things you will need to have set up:

  • PHP >= 7.1 installed on your system (you can confirm this by running the command php -v on your terminal)
  • Composer installed globally (confirm this by running the composer command on your terminal)
  • A GitHub account
  • A CircleCI account

Once you have these up and running, you will be ready to follow along.

Cloning the sample project

To begin, clone this Laravel project. It already contains some tests that we will be automating with CircleCI.

Once you have cloned the project, create a new GitHub repository and push the sample project to it. Let’s get familiar with the project you just cloned. Install the dependencies for the project by running the command:

composer install

Next, create a file named .env at the root of the project and copy the contents of .env.example into it (if you are using Linux you can simply run cp .env.example .env). This file should be ignored in your .gitignore file (this has already been ignored in the .gitignore of the cloned project).

Run the following command to generate an application key for the Laravel project:

php artisan key:generate

Now, run the project in your browser by running the following command:

php artisan serve

Navigate to the project homepage in your browser by visiting the URL http://127.0.0.1:8000. You will see the page displayed below:

It’s a simple homepage quite similar to the default page that comes with every new Laravel project. In this case, the page contains just a simple link that says Click Me which, when clicked, redirects you to http://127.0.0.1:8000/myresponse.

The /myresponse route simply displays Here is my response on the screen as shown below.


We will be testing this behaviour later on.

The project also contains a .env.testing environment file which has been set up solely for the purpose of our test cases. The application tests can be found in the tests folder.

Three types of tests have been set up in the tests folder. A unit test file in the Unit folder (ExampleUnitTest.php), another file containing HTTP tests in the Feature folder (ExampleHttpTest.php), and finally, a browser test file in the Browser folder (ExampleBrowserTest.php).

In this post, we will automate these tests by adding them to our CircleCI configuration.

I’m sure you’re pumped up for that. Let’s begin, shall we?

Adding unit tests to CircleCI configuration

The first set of tests we are going to add to our CircleCI config are our unit tests contained in the tests/Unit/ExampleUnitTest.php file.

This file contains two test cases as shown below.

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

public function testUserCreation()
{
    $user = new User([
        'name' => "Test User",
        'email' => "test@mail.com",
        'password' => bcrypt("testpassword")
    ]);   

    $this->assertEquals('Test User', $user->name);
}

The first test comes by default with any Laravel project and simply asserts the boolean true.

The second test creates a new user instance with the User model and checks the name of the newly created user instance against the expected value to assert a match.

Let’s now automate this test to run each time we push to our repository.

Go into the root of your project and create a folder named .circleci. Inside this folder, create a file named config.yml.

We are going to be performing the following tasks to automate our unit tests:

  • Spinning up the required environment
  • Installing dependencies with composer
  • Caching dependencies
  • Setting up a .env environment file for the project
  • Running our unit tests

Paste the following code into your config.yml file:

# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate

      # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit


In the configuration above, we begin by pulling an appropriate PHP image and checking out our code into the environment. We create our environment file from .env.testing.

jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env

We then install our dependencies and with the artisan command now in place, we generate an application key that is required by Laravel.

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate

With all required assets in place, we can then run our unit tests in the tests/Unit folder.

      # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit

Good.

Now push your updates to your GitHub repository.

Connecting the API project to CircleCI

Our next task is to set up our project on CircleCI. Head to your CircleCI console and add the project in the Add Project section.

Next to your project, click Set Up Project. This should bring you to a page similar to the one below.

Click Start building to begin building your project. CircleCI will begin to run your pipeline configuration, and you will get a successful build indicated by the screen below.

Click into this workflow and scroll down to Run Unit Tests to see the result of your tests.

Perfect.

Adding http tests to CircleCI configuration

The next task to automate is our http tests. The project contains an API route contained in the routes/api.php file.

Route::post('createuser', 'UserController@createUser');

This route maps to a createUser method in the UserController controller. This method creates a new user in the database and returns a success message in the form of the string “Successfully created user” and a status code of 201(Created).

A test has been written for this endpoint to assert the status code and success message for successful user creation. This test is contained in the file tests/Feature/ExampleHttpTest.php.

public function testUserCreationEndpointTest()
{
    $name = $this->faker->name();
    $email = $this->faker->email();
    $password = "mypassword";

    $response = $this->postJson('/api/createuser', [
        'name' => $name, 
        'email' => $email,
        'password' => $password,
        'password_confirmation' => $password
    ]); 

    $response
        ->assertStatus(201)
        ->assertExactJson([
            'message' => "Successfully created user!",
        ]);
}

This test creates a new dummy user by calling the createuser endpoint and then checks the response code and response body to see that it matches what is expected.

Now let’s automate this test in our CircleCI configuration. We will need to do the following to our existing configuration to make that happen:

  • Create a sqlite database named database.sqlite in the database folder (.env.testing uses the sqlite database for tests. The sqlite configuration in config/database.php has also been defaulted to use this database)
  • Run migrations
  • Run Http tests with PHPUnit

Add the following steps to your CircleCI configuration file:

	  - run:
	      name: "Create database and run migration"
	      command: |
	        touch database/database.sqlite
	        php artisan migrate --env=testing

	  - run:
	      name: "Run Http Tests"
	      command: ./vendor/bin/phpunit tests/Feature

In the above commands, we first create our database file in the database folder. We then run our migrations specifying a test environment.

In the second step, we run our http tests contained in our tests/Feature folder.

Save the file and push your changes to your repository. This should trigger the CI/CD pipeline and the tests should run successfully as indicated below.

Awesome.

Adding browser tests to CircleCI configuration

The last set of tests we will be adding to our configuration is the browser tests.

Laravel uses the laravel/dusk package to run tests directly in your browser. By default, laravel/dusk uses Google Chrome and a standalone ChromeDriver installation to run your browser tests, but you can use other browsers if you have your own Selenium server set up.

The Chrome driver already comes along with an installation of laravel/dusk package so you don’t need to worry about installing it separately.

By default, laravel/dusk tries to start the Chrome driver when the dusk command is run to initiate the browser tests, but in our case we would like to start the driver manually in order to be in total control of the process. Therefore, we will comment out the line that starts the Chrome browser automatically in tests/DuskTestCase.php as shown below:

public static function prepare()
{
    // static::startChromeDriver();
}

This has already been done in the cloned project so you don’t need to do it yourself.

Note: DuskTestCase.php and the tests/Browser directory do not come by default with a new Laravel project. They are created when laravel/dusk is installed by running:

composer require --dev laravel/dusk
php artisan dusk:install

More details about laravel/dusk and how it operates can be found in the Laravel Documentation page for the package.

We already know that our application has a homepage with a link labeled Click Me. When it is clicked, it redirects to a route that displays the string, “Here is my response”.

Inside our ExampleBrowserTest.php browser test file contained in the tests/Browser folder, we have a test that asserts this behaviour and also checks to see if the big Laravel label can be found on our homepage.

public function testBasicExample()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertSee('Laravel');
    });
}

public function testLink()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->clickLink('Click Me')->assertSee("Here is my response");
    });
}

To run our browser tests, we need to add the following to our CircleCI configuration:

  • Install the laravel/dusk package
  • Install staudenmeir/dusk-updater package which we use to update our Chrome drivers to the version of Chrome browser available on the Docker image
  • Start the appropriate Chrome driver for our image (in this case Linux)
  • Serve the application
  • Run our browser tests

Add the following steps to your CircleCI configuration.

	  - run:
	      name: "Install Dusk and Update Chrome Drivers"
	      command: |
	        composer require --dev laravel/dusk:"^4.0"
	        composer require --dev staudenmeir/dusk-updater
	        php artisan dusk:update --detect

	  - run:
	      name: Start Chrome Driver
	      command: ./vendor/laravel/dusk/bin/chromedriver-linux
	      background: true

	  - run:
	      name: Run Laravel Server
	      command: php artisan serve
	      background: true

	  - run:
	      name: Run Browser Tests Tests
	      command: php artisan dusk

We start by installing laravel/dusk, then the staudenmeir/dusk-updater package which enables us to update our chrome drivers.

Next, we start the Chrome driver and the Laravel application server in the background. We ensure that they run in the background by adding background: true to both steps.

Finally, we run our browser tests by running the dusk command.

Below is the complete config.yml file

# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
  build:
    docker:
      # Specify the version you desire here
      - image: circleci/php:7.4-node-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update
            sudo docker-php-ext-install zip

      - run:
          name: "Create Environment file"
          command: |
            mv .env.testing .env

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

      - run:
          name: "Generate App key"
          command: php artisan key:generate

      # run tests with phpunit
      - run:
          name: "Run Unit Tests"
          command: ./vendor/bin/phpunit tests/Unit

      - run:
          name: "Create database and run migration"
          command: |
            touch database/database.sqlite
            php artisan migrate --env=testing

      - run:
          name: "Run Http Tests"
          command: ./vendor/bin/phpunit tests/Feature

      - run:
          name: "Install Dusk and Update Chrome Drivers"
          command: |
            composer require --dev laravel/dusk:"^4.0"
            composer require --dev staudenmeir/dusk-updater
            php artisan dusk:update --detect

      - run:
          name: Start Chrome Driver
          command: ./vendor/laravel/dusk/bin/chromedriver-linux
          background: true

      - run:
          name: Run Laravel Server
          command: php artisan serve
          background: true

      - run:
          name: Run Browser Tests Tests
          command: php artisan dusk

With this in place, push your changes to the repository. The tests will run successfully with a screen similar to the one below:

Notice that the circular icons next to Start Chrome Driver and Run Laravel Server are greyed out. This indicates that these processes are running in the background as specified in the configuration.

Conclusion

In this post, we have been able to learn how to add different types of Laravel tests to our CircleCI configuration and how CircleCI makes running these tests a breeze. If some of your tests are not successfully running, try to go through the post once again so see if you have missed any steps. I am confident you will be able to find the missing piece.

Wishing you a bugless coding week :)


Fikayo is a fullstack developer and author with over a decade of experience developing web and mobile solutions. He is currently the Software Lead at Tech Specialist Consulting and develops courses for Packt and Udemy. He has a strong passion for teaching and hopes to become a full-time author.