iOS Continuous Integration Using Travis CI and Fastlane

Continuous Integration (CI) is a powerful practice to keep any app in a stable state throughout its development. Here's how you set up CI for iOS apps to automatically perform code validation and testing whenever your code changes.

Written by 

This post will cover an easy-to-setup approach to CI for iOS apps. The setup uses Travis CI as CI provider, the reason being that Travis CI is cloud-based and has a free-plan for open-source projects - even for building on macOS (usually not the case with other CI providers). So let's not waste time and take our apps to the next level!

Setting up Travis

So I'm just going to quote the initial setup from the Travis CI docs here:

  • Go to Travis-ci.com and Sign up with GitHub.
  • Accept the Authorization of Travis CI. You’ll be redirected to GitHub.
  • Click the green Activate button, and select the repositories you want to use with Travis CI.

Bundler + Gemfile dependencies

We'll be using Bundler so we have a Gemfile specifying all the RubyGem dependencies that we need. That will make it easier when we need to install the dependencies on Travis CI.

If you don't have Bundler installed already on your Mac, do it by running gem install bundler. If you don't have permissions, you need to have rbenv installed or sudo it if you're brave.

Navigate to the directory of your project and run bundle init. That will create a Gemfile for you. Here are the dependencies we need to install, so copy/paste this code into your newly created Gemfile:

source "https://rubygems.org"

gem "danger"
gem "danger-swiftlint"
gem "fastlane"
gem "xcode-install"

Now that we've specified the dependencies we need, install them by running bundle install. This also adds a Gemfile.lock to your project, specifying the versions of the dependencies that were installed.

Travis CI environment variables

We need to provide Travis CI with two access tokens to do its thing. Go to GitHub's Personal Access Token in Settings and generate a new access token. You can name it "travis" if you like. Then you need to go to the environment variables section of your repo in Travis CI. It should be under Settings. You then need to create these two environment variables and copy/paste the access token into them:

  • DANGER_GITHUB_API_TOKEN
  • GITHUB_API_TOKEN

That should give Travis CI the access it needs.

Creating .travis.yml with stages/jobs

Now, onto the more interesting stuff. Create a file in the root of your project and name it ".travis.yml". This is where the CI magic happens - here, we will define all the jobs that we want to run. Naturally, depending on your project, this file will differ greatly. So I'll just share how it looks in my app QCards and take it from there:

# environment setup
language: swift
osx_image: xcode11.1

# caching of dependencies
cache:
  bundler: true
  directories:
    - Carthage

# run jobs on changes to master only
branches:
  only:
    - master

# prepare dependency managers
before_install:
  - brew update
  - brew outdated carthage || brew upgrade carthage
  - gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)"

# install dependencies
install:
  - travis_wait carthage bootstrap --platform iOS --cache-builds
  - bundle install

# jobs and stages to run
jobs:
  include:
    - stage: "Code validation"
      name: "Code validation"
      script:
        - bundle exec danger

    - stage: "Test"
      name: "Unit tests"
      script:
        - bundle exec fastlane unit_test

    - stage: "Test"
      name: "UI tests"
      script:
        - bundle exec fastlane ui_test

What you need to take away from this is that we have two stages: "Code validation" and "Test".

  • "Code validation" is our first stage which contains a single job that triggers Danger. If all of the jobs in a stage passes, we proceed to the next stage. In this case, we only have one job that needs to pass to proceed.
  • Our second stage "Test" contains two jobs that will run in parallel: "Unit tests" and "UI tests". The command bundle exec fastlane [lane_name] simply triggers a lane in our Fastfile which, in turn, triggers the execution of the tests.

if you're not familiar with the concepts of jobs and stages, I recommend that you read about the job lifecycle and build stages from the Travis CI docs.

Code validation using Danger + SwiftLint

Danger is one of the dependencies we specified in our Gemfile earlier. It's a great tool to improve our code reviews and simplify pull requests to our project. The Danger docs describe itself like this:

Danger runs during your CI process, and gives teams the chance to automate common code review chores.

These chores could be anything from formatting issues in the code to formalities such as not leaving a description in the pull request. You decide on the rules using the Dangerfile. What happens is that Danger will leave messages in your pull requests when you violate any of the rules. Here's the rules that I use in this Dangerfile and you can expand it with your own rules.

We also specified "danger-swiftlint" in the Gemfile. It's a plugin for Danger that allows us to use SwiftLint in combination with Danger to perform linting and report back if there are any violations. Create a ".swiftlint.yml" file in the root of your project. You can copy/paste the linting rules that I normally use here. Lastly, make sure that you have these two lines in your Dangerfile:

swiftlint.config_file = '.swiftlint.yml'
swiftlint.lint_files inline_mode: true

That's the code validation! Now, onto the automated testing.

Testing using Fastlane scan

We are using Fastlane scan to run our tests. So we need to set up Fastlane by running fastlane init at the root of our project and select: "4. Manual setup - manually set up your project to automate your tasks". That will create a Fastfile inside the newly created fastlane folder.

Now, go ahead and run fastlane scan init in your project to create a Scanfile inside the fastlane folder. This is a configuration file with all the default parameters we want to set when we run the tests. It could look like this, but you want to check the docs to see what makes sense in your project:

devices(["iPhone 11 Pro"])
reset_simulator(true)
clean(true)

In your Fastfile, create one or more lanes where you'll run your tests. In my case, it looks like this:

setup_travis

default_platform(:ios)

before_all do
  xcversion(version: "11.1")
end

platform :ios do
  desc 'Runs the unit tests in QCardsTests'
  lane :unit_test do
    scan(scheme: "QCardsTests")
  end

  desc 'Runs the UI tests in QCardsUITests'
  lane :ui_test do
    scan(scheme: "QCardsUITests")
  end
end

It goes without saying that you should change the code to match the schemes in your project.

Now you should be all set with the testing. If you've come this far, you should be able to create a pull request with changes to your code and see the CI in action. If everything is successful, you should see a screen like this in Travis CI:

Conclusion

That's it! A good CI setup can make you and your team members lives a whole lot easier. It may even determine if your app makes or breaks it during development. You can find the source code for this exact setup on GitHub.

In the next posts, we'll expand upon this setup so that we can automate code signing and deployments to App Store.

Thanks for following along!

1 thought on “iOS Continuous Integration Using Travis CI and Fastlane”

  1. Pingback: Continuous Deployment For iOS Apps Using Travis CI and Fastlane - Andreas Lüdemann

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.