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 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 ""

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:


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
  bundler: true
    - Carthage

# run jobs on changes to master only
    - master

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

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

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

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

    - stage: "Test"
      name: "UI tests"
        - 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"])

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



before_all do
  xcversion(version: "11.1")

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

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

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:


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!

Share this post


You may also like


How to Generate API Client Code in React (Native) Apps

Keeping your API client code updated whenever there’s a change in the back-end rest apis and models is tedious and error prone as a front-end developer. A better way would be if the API client code was kept updated automatically so you have one less thing to worry about.


Automate Screenshots for App Store Using Fastlane

Save yourself from spending hours on screenshots for the App Store. Here’s a step-by-step guide on how you automate the process of taking screenshots for all supported screen sizes and languages.


7 Useful Filtering Operators in RxSwift

After using RxSwift for several months now, I can safely say that filtering is a key feature that has saved me plenty of lines of code. To know which filtering operators to use when, requires a basic overview, so let’s look at 7 of my favorite filtering operators this library has to offer.