Automate Tests in iOS with Git Hooks

I leant about ⌘ + B a long time before ⌘ + U. It’s unrealistic to expect yourself to reliably run tests at suitable intervals throughout the day. Or, at least, it’s a hard habit to form, especially for us iOS developers.

This is where git hooks and automation come in to give you one less thing to remember to do.

Running Tests through the Command Line

Let’s start with being able to run Xcode tests from the command line. First, make sure you’ve downloaded and installed Xcode command line tools (You’ve probably got this already).

Next, make a new project and make sure to tick the option to include unit tests, or you can add a test target manually. Make a simple test that passes, XCTAssert(true) should do.

Now let’s use xcodebuild to run the project’s tests from the command line. The format of the command is:

Note: you can use -workspace and YourProject.xcworkspace instead of project if you’re using a workspace.

Give it a run in your project directory and if everything goes according to plan it’ll spew out a lot of noise and after a few seconds it should exit with ** TEST SUCCEEDED ** at the bottom. Great! We’re halfway there. If it didn’t work then you’ll have to do some Googling.

Fishing for Commits

Git has a powerful, underused feature called hooks which allow scripts to be run just before key events like committing and pushing, potentially stopping them from being executed.

Let’s create a script that will run our tests when we use the commit command and only complete the commit if all our tests pass.

Make a scripts folder in your project directory and create a install-hooks.bash bash script inside it. This script will create a symlink to another script that will be run prior to committing. Paste in the following code:

Before we run this let’s create the script that will run our tests. Make `pre-commit.bash` in the same folder and paste in the code which uses the same command we used earlier to run the tests:

The git pre-commit hook will not allow the commit to be carried out if anything other than a zero exit code is returned from our tests, which is what the last 4 lines are doing.

Of course, it would be good to know how to override this if you’ve been asked to rush something in. We can do this with:

One last thing to do — make both scripts executable by running:

With all of that done run ./install-hooks.bash to make the symlink. If we navigate to the .git/hooks folder we can verify that the symlink has been created.

The point of making these two scripts is so that we can easily pass them to our colleagues to allow them to replicate our behaviour.

Wrapping Up

Now whenever we try and commit something the tests will be run and the commit will only happen if they all pass, letting you focus on writing good code rather than remembering to continually run your tests 👍🏻

If your tests take a while to execute and you don’t want to have to run them every time you commit you can replicate this behaviour in the pre-push hook instead.

Hopefully this will save you and your team some time. Let me know if there are any other improvements that you use.

Alternative Approach

The alternative to this is to use Continuous Integration (CI) e.g. only allowing the merge of a pull request to the remote master branch if all tests pass. However, this is more complex to setup, and not everyone has access to something like Jenkins.

Sources/Further Reading

iOS Developer, London

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store