Laravel Continuous Integration using Codeship

In my last blog post, I talked about how we test our Laravel applications. In this post, I’d like to expand on how we’ve set things up for Continuous Integration using a service called Codeship.

First of all, let me define the term. Wikipedia defines it as “In software engineering, continuous integration (CI) is the practice of merging all developer working copies to a shared mainline several times a day”. In practice, for us, it means that my team and I can create Pull Requests for our projects at any time, and our continuous integration will run on that branch and either give it a green flag, or point out any issues that might be happening.

We actually have two different CI services setup, Codeclimate, which we use for linting, coding standards and other code quality tools (I’ll have to blog about this later); and Codeship which we use to run all of our tests.

Part 1: setting up the world

The first thing you’ll do after signing up for a Codeship account is to connect it to your version control repository. For us that was Github, but they also support other providers. Next, you’ll be asked to configure your test pipeline(s). There’s two (or more if you have several test pipelines) steps to this. The first one is “Setup Commands” where you can setup the world. Basically, whenever Codeship runs the tests on your code, it’ll prepare a new fresh virtual machine with your latest code checked out on it. The setup commands are the commands that run immediately after that is done. Ours look like this:

# set php version
phpenv local 7.1
phpenv global 7.1
# node version
nvm install 7.6.0
# Install yarn
npm i -g yarn
# Install node-sass
yarn global add node-sass
# Optional GitHub auth for composer
composer config -g github-oauth.github.com [token]
# Install dependencies through Composer 
composer install --prefer-dist  --no-interaction
# set phpunit version
composer global require "phpunit/phpunit=5.*"
# copy env
cp -v .env.example .env
# generate key
php artisan key:generate
# front-end
yarn
npm rebuild node-sass
yarn build

I’ll break this down a bit:

  • First up, we setup the php and node versions that we want to use.
  • Secondly, I prefer working with yarn over npm as it is more reliable and locks dependency versions which prevents issues with version mismatches. So I install yarn. This part is optional if you prefer to use npm or don’t need to compile any front-end resources.
  • We use sass to write our css, and Codeship doesn’t come bundled with node-sass, so next up we install it.
  • Next, we optionally configure Composer’s GitHub auth with a token. This speeds things up a bit with composer installs, read more about it here. Then, we install Composer dependencies.
  • Next, we set the version of phpunit to be used, 5.x in our case at the time of writing.
  • Finally we do a bit of Laravel and project specific setup, copy our .env.example file, generate an application key, and run yarn to compile our front-end resources

Part 2: Unit & Feature tests

Screen Shot 2017-04-08 at 2.35.32 PM.png

By default, Codeship provides you with a single test pipeline, named “Test Commands”. We use this default one to run our Unit and Feature tests with phpunit.

The test command here is super simple:

php vendor/bin/phpunit

This just runs phpunit using the version that’s installed with composer locally for the project.

Part 3: Browser & JS tests

We use a second test pipeline to run our Browser (Dusk) tests and Javascript (jest) tests. This step does require a small change to Laravel’s default DuskTestCase to work correctly on Codeship.

We modified the driver() method in the tests/DuskTestCase.php file, like so:

/**
 * Create the RemoteWebDriver instance.
 *
 * @return \Facebook\WebDriver\Remote\RemoteWebDriver
 */
protected function driver()
{
    $chromeOptions = new ChromeOptions();
    if ($binary = env('DUSK_CHROME_BINARY')) {
        $chromeOptions->setBinary($binary);
    }
    $chromeOptions->addArguments(['no-first-run']);
    $capabilities = DesiredCapabilities::chrome();
    $capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions);

    return RemoteWebDriver::create(
        'http://localhost:9515',
        $capabilities
    );
}

Next, we create a separate .env.dusk.testing file for dusk specific environment configuration. In order to get Dusk running correctly on Codeship, you will need to specify the APP_URL as [http://lvh.me](https://documentation.codeship.com/basic/continuous-integration/using-subdomains/%5D so that Dusk has a URL to hit.

Finally, we can go back to the pipeline in Codeship and add the following commands:

# don't start chromedriver automatically
export DUSK_START_CHROMEDRIVER=false
export DUSK_APP_PORT=8000
# use correct version of chrome (57 )
ln -sf /usr/bin/google-chrome /home/rof/bin/chrome
export DUSK_CHROME_BINARY=/home/rof/bin/chrome
# startup chrome driver manually
nohup bash -c "./vendor/laravel/dusk/bin/chromedriver-linux 2>&1 &"
# boot up the larval app
nohup bash -c "php artisan serve --env=dusk.testing 2>&1 &" && sleep 4
# run dusk
php artisan dusk
# run our javascript tests
yarn test

First we setup some environment variables to overwrite the Dusk chrome driver auto-start, and set the port. Then we set the correct version of Chrome to use (by default Codeship uses chromium with an outdated version that doesn’t work with the latest webdriver…). Then we boot up the chromedriver, and the Laravel app using the built-in artisan php server.

Then we run our dusk tests, and finally our jest tests.

Putting it all together

As a last configuration step, we need to set environment variables using the Environment settings tab for the project in Codeship; this will set the correct settings to use for testing.

Screen Shot 2017-04-08 at 2.44.25 PM.png

These should hopefully be self-explanatory and are documented in the Laravel docs. We do also set the COMPOSER_HOME value so that Composer uses its internal cache to speed things up.

That’s it, from now on, whenever you push code to your repository, Codeship will run tests and notify you if any tests fail!

Screen Shot 2017-04-08 at 2.51.46 PM.png


Have you setup CI for your projects? Any struggles or tips you’d like to share? Let me know in the comments 💭👇.

Test Driven Development with Laravel

At Spark Consulting, we put a great emphasis on automated testing of our code. Doing so has a bunch of benefits:

  • we can ship things with confidence knowing they won’t cause any regressions or unintended side effects
  • it makes it easier to review changes because I can easily see how a piece of code is intended to work by reading the test; tests essentially act as developer documentation
  • allows developers to catch mistakes in their code before committing/deploying
  • it keeps code quality high by ensuring we all follow the same sets of conventions
  • it’s actually fun; it’s a really great and rewarding feeling to see the following whenever you run your tests after writing some code:

[f5141f4250a86c6936323c1f4cc645be]_Image202017-04-0820at201.07.0320PM

  • stress-free deployments, knowing that the chances you unintentionally broke something are pretty low

Our development framework of choice, Laravel has a bunch of awesome testing tools built-in which make all of this really easy.

Our tests are organized as following:

Screen Shot 2017-04-08 at 1.17.51 PM

From bottom to top:

  • Unit contains our unit tests; most projects actually have very few of these. We mostly use unit tests to test a few key pieces of custom functionality and relationships between Models. Unit tests don’t generally load the entire Laravel application. Almost everything else in the php codebase is tested as a Feature test.
  • React (sometimes also just named js depending on the project) contains tests written using the jest test runner and in the case of React, the enzyme test helper . These tests are meant to test front-end Javascript functionality, such as React components and Javascript helpers/utilities that we’ve written for the front-end.
  • Feature contains tests that ensure that our php codebase works as expected by loading the entire Laravel application and performing full Http requests. These are primarily API tests to ensure our API endpoints work as expected, but anything that is php-driven and requires the application to test would be a Feature test in my approach.
  • Browser contains end-to-end integration tests that test the web application in a browser directly, using the new Laravel Dusk testing utility. We use these to test individual pages at a pretty high level and ensure that key data and key functionality works as expected.

Having these different levels of testing allows us to ensure the application works correctly from different angles, and covers all of our bases. Think of Browser tests sort of being the super zoomed-out view, where we can ensure that the main pages and functions of the site work as expected, and then we slowly zoom in with feature tests, js tests and unit tests ensuring that each piece of our codebase works as expected.

With this setup we end up with three different testing commands:

  • phpunit which runs both our Unit and Feature tests
  • php artisan dusk which runs the browser tests
  • jest (which we alias in our package.json so we can run npm run test or just yarn test using yarn) to run the Javascript tests.

If you’d like to learn more about Test Driven Development with Laravel, I highly recommend Adam Wathan’s Course. Even though I already grasped the basics, Adam’s course really cemented some best practices and showed me a better way of thinking and approaching tests.

In the next blog post, I’ll go through our Continuous Integration setup and explain how we automate all of this testing so that each Pull Request/deployment gets automatically tested.

%d bloggers like this: