FacebookLinkedInShare

Sometimes as developers we are under high stress and the daily tasks and pressure to deliver might catch us at a situation where we forget to run the unit tests after making changes
to the code. This article is here to help you prevent such scenarios by running the unit tests for you automatically before committing your code into Git. If that is not enough
we will make sure during this article that in case unit tests have failed your commit will simply be rejected until you fix your code.

This guide is written for a PHP project but can work just as well for any other kind of language or technology stack.

We will attack this problem using your development IDE (In this example PHPStorm from JetBrains), using Grunt which is a javascript task runner / build tool and using one of it’s many plugins
in our specific case we will use grunt-phpunit and grunt-karma. This document can work for you even if you run unit tests using Ruby, JUnit , Karma or whatever else you want to do.

The basic concept of hooking a process in a Git repository before committing is a professional and effective practice no matter which technologies you use on your day to day.

Enough talking, let’s get our hands dirty!

This guide assumes you already have Node.js installed on your system. if it’s not – go through this guide first (http://coolestguidesontheplanet.com/installing-node-js-osx-10-9-mavericks/)
We are using Mac OS X Yosemite on a MacBook Pro for this guide

  1. Installing grunt and grunt-cli globally so it can be accessed from all projects and directories using our iTerm2 or equilavent terminal application
    1. Fire up your terminal
    2. npm install -g grunt-cli
    3. Grunt requires a Gruntfile.js and a package.json (which is similar to a Gemfile in a Ruby on Rails project) to know which tasks to run
    4. The package.json of the project looks like so (For our example we have unit tests in PHP using PHPUnit):
      
      {
        "name": "My Project",
        "version": "1.0.0",
        "dependencies": {
          "karma": "= 0.12.31",
          "karma-jasmine": "= 0.3.5"
        },
        "devDependencies": {
          "grunt": "^0.4.5",
          "grunt-contrib-watch": "^0.6.1",
          "grunt-phpunit": "^0.3.6",
          "grunt-karma": "^0.11.1",
          "jasmine-core": "^2.1.3",
          "karma": "^0.12.31",
          "karma-chrome-launcher": "^0.1.12",
          "karma-coverage": "^0.3.1",
          "karma-firefox-launcher": "^0.1.6",
          "karma-ie-launcher": "^0.1.5",
          "karma-jasmine": "^0.3.5",
          "karma-junit-reporter": "^0.2.2",
          "karma-phantomjs-launcher": "^0.2.0",
          "phantomjs": "^1.9.17"
        }
      }
      
    5. The second file we need is the Gruntfile.js which holds the description(s) of the tasks we want Grunt to run for us
      (as of this moment we are only running unit tests for the server side code using Grunt, but we can also automate other actions like compressing Javascript, compile stuff , etc
    6. 
      // Gruntfile.js
      module.exports = function (grunt) {
          grunt.initConfig({
              phpunit: {
                  classes: {
                      dir: 'tests/'
                  },
                  options: {
                      logTap: '/var/log/your-project/tests.log',
                      configuration: 'phpunit.xml',
                      colors: true
                  }
              },
              karma: {
                unit: {
                   configFile: 'conf.js',
                      singleRun: true,
                      browsers: ['PhantomJS']
                 }
              },
              watch: {
                  test: {
                      files: ['app/**/*.*', 'public/**/*.php', 'tests/*.*'],
                      tasks: ['phpunit']
                  }
              }
          });
       
          grunt.loadNpmTasks('grunt-contrib-watch');
          grunt.loadNpmTasks('grunt-phpunit');
          grunt.loadNpmTasks('grunt-karma');
       
      };
      

      As you can see, the Gruntfile.js lists 2 tasks to be run while the one we care about in this article is only the grunt-phpunit and the grunt-karma task.
      In it’s definition we can see we pass the plugin the tests folder and an optional log file to output all results of running the unit tests for later anaylsis should we need them.

    7. Now up until this point if we are to go to the console and run the following command, Grunt would run and perform our unit tests while on the repository of your-project (path wise):
      your-project git:(master) $ grunt phpunit
      Running "phpunit:classes" (phpunit) task
      Starting phpunit (target: classes) in tests/
      PHPUnit 4.5.0 by Sebastian Bergmann and contributors.
      Configuration read from /Users/itamar.arjuan/dev/repositories/your-project/tests/phpunit.xml
      ...............................................................  63 / 135 ( 46%)
      ............................................................... 126 / 135 ( 93%)
      .........
      Time: 739 ms, Memory: 20.75Mb
      OK (135 tests, 247 assertions)
      Done, without errors.
       
      ?  your-project git:(master) ? grunt karma
      Running "karma:unit" (karma) task
      INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
      INFO [launcher]: Starting browser PhantomJS
      INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket 7Kr7OEujbZ4Mh8AgwkKy with id 85956981
      PhantomJS 1.9.8 (Mac OS X): Executed 11 of 11 SUCCESS (0.006 secs / 0.007 secs)
      Done, without errors.
      

      We can see that Grunt has ran out tests successfully! The next stage would be to make sure that while commiting files to the Git repository this task will run and prevent us from commiting broken code to the code repository. To do that we will use another bash script that will run this grunt command and return an exit code of 0 or 1 based on the success of the grunt task.

    8. The following script is also part of the your-project repository so that changes to it makes it easy to see for all people in the project , this script will be our Git pre-commit hook using a Symbolic link
      
      #! /bin/sh
      PATH=$PATH:~/usr/local/bin
      PATH=$PATH:/usr/local/bin
      export PATH="$(brew --prefix homebrew/php/php54)/bin:$PATH"
       
      grunt phpunit
      grunt karma
       
       
      RETVAL=$?
       
      if [ $RETVAL -ne 0 ]
      then
       exit 1
      fi
      
    9. As for your-project developers everything is already done when pulling the code from the your-project master branch. The only thing that needs to be done after we installed Grunt and grunt-phpunit is to create the symbolic link, the following code is valid given the correct path to the repository of the your-project in your local machine. In my computer the your-project repository resides at the following path:~/dev/repositories/my-project/and the file we just showed above is called pre-commit.sh from within that repository.We will run the following commands in order to link this bash script as the pre-commit hook for our your-project Git repository like so:
      
      $ cd ~/dev/repositories/your-project/.git/hooks
      $ ln -s ~/dev/repositories/your-project/pre-commit.sh pre-commit
      $ sudo chmod +x pre-commit
      

      After running these commands , running a list command on our hooks folder should look like so:

      
      ?  hooks git:(master) ll
      total 88
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   452B Dec  1  2014 applypatch-msg.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   896B Dec  1  2014 commit-msg.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   189B Dec  1  2014 post-update.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   398B Dec  1  2014 pre-applypatch.sample
      lrwxr-xr-x  1 itamar.arjuan  CORP\Domain Users    60B Jun 25 14:14 pre-commit -> /Users/itamar.arjuan/dev/repositories/your-project/pre-commit.sh
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   1.6K Dec  1  2014 pre-commit.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   1.3K Dec  1  2014 pre-push.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   4.8K Dec  1  2014 pre-rebase.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   1.2K Dec  1  2014 prepare-commit-msg.sample
      -rwxr-xr-x  1 itamar.arjuan  CORP\Domain Users   3.5K Dec  1  2014 update.sample
    10. Now if we try to commit code using PHPStorm with a broken tests, we will get a similar response from the IDE when trying to commit:
      Screen Shot 2015-06-25 at 2.19.11 PM
    11. The message will be saved in the “Messages” tab (CMD + 0), So you’ll be able to check which tests failed.
    12. That’s it! you have a protected working environment and you cannot break unit tests anymore
    13. This hook can just as easily be done on RubyMine for all Ruby projects or any other language / technology stack (For all client side tests as well such as Karma / Mocha based unit tests of Javascript code.)
  • scofennell

    Thanks for this post! Was wondering if you could help me debug my own implementation, which happens to be in a WordPress plugin. phpunit itself seems to work fine, but when I run grunt phpunit, none of the tests run. Here’s the output:

    scotts-MacBook-Pro:lxb-zendesk scottfennell$ grunt phpunit
    Running “phpunit:classes” (phpunit) task
    Starting phpunit (target: classes) in tests/
    Installing…
    Installing network…
    Running as multisite…
    Not running ajax tests. To execute these, use –group ajax.
    Not running ms-files tests. To execute these, use –group ms-files.
    Not running external-http tests. To execute these, use –group external-http.
    PHPUnit 5.2.3 by Sebastian Bergmann and contributors.
    Time: 1.43 seconds, Memory: 41.00Mb
    No tests executed!

    I’m not sure where to go from here. Any advice?