Live Coding: Continuous Integration with Salesforce

Last Thursday Alix Ohrt and I did a Live Coding session on adding Continuous Integration (CI) to existing Salesforce projects. If you were able to tune in, this post should be a great resource for following up in your own org. If you weren’t able to join us live, this is a nice overview of what we talked about. If you wanted to join, but couldn’t, don’t fret — we have the recording right here! With that, let’s dive in and add CI to our Salesforce project.

What is CI?

If you get ten developers in a room and give them an hour to define Continuous Integration (CI), I’m confident you’d end up with ~30 definitions. For our purposes, CI is the process of automating parts of your development workflow. There are parts of the development workflow that are tedious, time consuming and/or just … well, boring (like running the full suite of unit tests, or making sure the formatting of my code matches the standards everyone else on the team expects). The good news is that these things — and more! — can all be automated. Unfortunately, the ‘gotcha’ with CI is that it can only notify you when issues are found. It can’t fix them for you. CI isn’t going to be able to fix your architectural issues or write more meaningful unit tests, but it will tell you when a test is broken or when you’ve done something silly.

Automating unit testing with CircleCI

Classic Continuous Integration systems focuses on running unit tests, so let’s start there. There are a number of tools that facilitate this and we chose CircleCI. CircleCI is free for Open Source projects. You can sign up at CircleCI.com. Like many CI tools, CircleCI is configured via a config.yml file. Getting started is pretty easy, as CircleCI provides an example config file. You’ll first need to login to your CircleCI account and follow the add project guide. After authenticating to your version control system, selecting your repository and informing CircleCI what language your project is written in, you’re given an example config.yml file. That example config, shown below, is only really useful for logging information whenever a commit is detected.

To turn that config into something useful, we need to tell CircleCI what, and how, to run our unit tests. Our example project is DX-based, so we’ll need to:

  1. Download and install the SFDX command line utility
  2. Authenticate to our Dev Hub org
  3. Create a Scratch org
  4. Push our code
  5. Run the unit tests
  6. Save our results (including code coverage)
  7. Delete the Scratch org

We have to do all of these steps every time because the environment we’re testing in is ephemeral. That is, the Virtual Machine or Docker Image spun up by CircleCI to run our build script only lives long enough for the build to succeed or fail. Doing all these steps may seem tedious, but this is where automation really shines. Let’s look at the config.yml for CircleCI that automates this for us.

version: 2
jobs:
  build:
    machine: true
    working_directory: ~/ci_app
    environment:
      - DX_CLI_URL: https://developer.salesforce.com/media/salesforce-cli/sfdx-linux-amd64.tar.xz
    steps:
      - checkout

      - run:
          name: Install SFDX CLI
          command: |
            mkdir sfdx
            wget -qO- $DX_CLI_URL | tar xJ -C sfdx --strip-components 1
            ./sfdx/install

      - run:
          name: Create Scratch Org
          command: |
            openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K $DECRYPTION_KEY -iv $DECRYPTION_IV
            sfdx force:auth:jwt:grant --clientid $HUB_CONSUMER_KEY --jwtkeyfile assets/server.key --username $HUB_SFDC_USER --setdefaultdevhubusername -a hub
            sfdx force:org:create -s -f ~/ci_app/config/project-scratch-def.json -a circle_build_$CIRCLE_BUILD_NUM --wait 3
            sfdx force:source:push -u circle_build_$CIRCLE_BUILD_NUM

      - run:
          name: Run Apex Unit Test
          command: |
            mkdir -p ~/junit
            sfdx force:apex:test:run -c -d ~/junit -r junit --wait 5 -u circle_build_$CIRCLE_BUILD_NUM

      - store_test_results:
          path: ~/junit

      - run:
          name: Delete Scratch
          command: |
            sfdx force:org:delete -u circle_build_$CIRCLE_BUILD_NUM -p

If you look carefully, you’ll see this file has a number of run steps. Each has a name, describing what’s going on. They also have a Command clause, where shell commands are written.

Each run command corresponds to a section in the CircleCI UI. Here’s the output of the Create Scratch Org CLI run step.


Once our environment has the SFDX utility installed, we can authenticate to our Dev Hub. To do this, we’re going to decrypt a copy of our server key using CircleCI environment variables. Those variables start with a $ and are in all caps. CircleCI has a nice interface for setting up custom environment variables that allow you to keep secrets — like your Decryption Key and Decryption IV from having to be committed to your code!

The rest of this config.yml accomplishes the goals we set above. I want to draw your attention, however, to the penultimate step: store_test_results. This step is crucial to surface our test results in CircleCI. Once our tests results are stored, those results are available at the top of the build log. Here’s a screen shot from a commit I made with an intentionally bad test:


So that’s sorted — automated unit tests on each commit! But we can do more with CI tools. Let’s look at what we can do with Husky.

Pre-commit hooks with Husky

Husky is a NPM package designed to make pre-commit hooks easy. But first, you may want to know what a pre-commit hook is. Git has support for allowing developers to insert logic during specific places in the git workflow. One place is ‘pre-commit’ and any logic — usually shell scripts, but in this case NPM tools — designated as a pre-commit hook is executed by Git just before a commit occurs. Pre-commit hooks are useful for automating work we need done before the staged code is committed. Let’s look at what it would take to have Husky automatically format code to our teams’ standard, as well as lint our files as part of the commit process.

First, we need to ensure we have setup our project with NPM. If you have not already, run npm init from the command line in your project’s top-level directory. It’ll ask you a few questions, and then you’ll be off to the races. npm init creates a package.json file, and it’s here that you’ll need to add Husky. Look for the devDependencies key, and add Husky like this: ”husky”: “^2.4.1” You’ll also likely want to add ”prettier”: “^1.18”. Once you’ve added them, you just need to run npm install . This actually installs Husky and Prettier. Once that’s setup, you just need to tell Husky what, and when to run! Let’s look at its config, which is also stored in the package.json file.

  "husky": {
    "hooks": {
      "pre-commit": "lint-staged && npm run lint"
    }
  },
  "lint-staged": {
    "**/*.{html,js,json,yaml,yml,md,cmp,page,component}": [
      "prettier --write"
    ],
    "**/lwc/**": [
      "eslint"
    ],
    "*": [
      "git add"
    ]
  }

In this sample config, we’re telling Husky to setup a pre-commit hook that runs ‘lint-staged’ which we define right below. Lint-staged looks for all files that end in html, js, json, yaml, yml, md, cmp, page, component and runs prettier — an opinionated code formatter on those files. It also runs eslint on all the files in the lwc folder, and finally adds them to the git stage. Now, when I go to make a git commit, I see this output:

Since I can commit the package.json file, all my teammates need to do is run npm install and they automatically inherit all the automation we’ve set up.

Automated code reviews with Codacy.com

For our final tool, Alix and I worked through setting up Codacy. Codacy automates an important, if often overlooked, aspect of your coding workflow: code reviews. Codacy can’t (yet) replace human code reviews, but it can provide insightful feedback from static code analysis tools. Static code analysis tools inspect your code and look for structural issues — things like SOQL statements inside of for loops or overly-complex branching. When those issues are detected, Codacy notes them. Over time, as issues are added or resolved, Codacy gives you feedback telling you whether or not the code is up to standards. For instance, when I added my intentionally bad unit test we talked about above, Codacy kindly emailed me to let me know I could do better.


The great thing about Codacy is that it’s Salesforce aware out of the box. When I added my project to Codacy, it automatically enabled PMD, a static code analysis tool for Java-like languages. It then gave me a series of code patterns it would flag. Note there on the side, where it says Apex. Clicking Apex showed me the rules specifically for Salesforce Apex! Of course, if you want, you can upload your own PMD configuration file.

Check out the recording of the live coding session for a more detailed explanation. Alix and I really only scratched the surface of how CI can help automate your development workflow. Automating things like static code analysis and unit testing can help highlight — for example — opportunities for growth in your codebase, even if it can’t fix them for you. If you’d like to learn more about CI in general, we’ve put together a trailmix you can take. We’ve also made sure that our Sample Gallery apps all have CI enabled, so check them out to see more examples of CI at work.

Next week, Simon Goodyear and I will live code a solution to the problem of breaking up an org into separate packages, while keeping triggers clean and easy to use. Register for the session and join us!

About the author

Kevin Poorman is a Developer Evangelist at Salesforce. He focuses on Testing, Mobile, and Integrations on the Lightning Platform. You can pester him on Twitter @codefriar.

Leave your comments...

Live Coding: Continuous Integration with Salesforce