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
overnpm
as it is more reliable and locks dependency versions which prevents issues with version mismatches. So I installyarn
. This part is optional if you prefer to usenpm
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 runyarn
to compile our front-end resources
Part 2: Unit & Feature tests
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.
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!
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:
- 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:
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 namedjs
depending on the project) contains tests written using thejest
test runner and in the case of React, theenzyme
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 testsphp artisan dusk
which runs the browser testsjest
(which we alias in ourpackage.json
so we can runnpm run test
or justyarn 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.
#ThemeWeekend
June 30th and July 1st 2012 is the first ever Vancouver WordPress Theme Weekend. I’m part of the organizing team and I’m giving a quick intro to Github, which is where we’ll be hosting all of the teams’ themes. You can check out the slides of the GitHub intro below or download them as a PDF.
Please check out all details for the event at wpyvr.org/theme-weekend
Always logged in, using mu-plugins
I’ve decided to start posting handy/helpful code snippets here [Idea/inspiration from Bill Erickson].
So here’s my fist snippet! It allows you to remain always logged in when developing sites (Warning: do not use this on a production site!)
I like to put this code in the special mu-plugins folder that way it always runs. We hook into ‘admin_init’ and ‘init’ function, check if we’re logged in, and if not, logs us in. Change the 1
in the get_userdata()
function to match the user ID that you want.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* keeps a user always logged in | |
* don't use this on a production site, ever! | |
*/ | |
add_action('init', 'auto_login'); | |
add_action('admin_init', 'auto_login'); | |
function auto_login() { | |
if (!is_user_logged_in()) { | |
$user = get_userdata(1); // 1 being the ID that I want | |
wp_set_current_user($user->ID, $user->user_login); | |
wp_set_auth_cookie($user->ID); | |
do_action('wp_login', $user->user_login); | |
} | |
} |
Again, don’t use this on a production or even staging site. It’s only meant to be used in a local environment, where you are the sole developer/user of the site (since it always logs into the same user). Use it with caution, you’ve been warned!