Continuous Integration
Learning objectives
- Knows the term continuous integration.
- Knows how to use GitHub Actions for continuous integration.
Note! We are rewriting these parts to match Fly.io in the near future.
Continuous integration is a practice where developers integrate their code to a central repository whenever they have finished working on a small increment to the software, often multiple times a day. Continuous integration is typically accompanied with automated testing -- whenever code is added to the repository, automated tests are run to verify the new code does not include breaking changes. Here, we take a brief look at using GitHub Actions for continuous integration.
For demonstration purposes, we use the following application and tests as a starting point. As you may notice, response from the application is not what the test expects.
import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";
const app = new Application();
const router = new Router();
const hello = ({ response }) => {
response.body = "Hello world";
};
router.get("/", hello);
app.use(router.routes());
export { app };
import { superoak } from "https://deno.land/x/superoak@4.7.0/mod.ts";
import { app } from "./app.js";
Deno.test("GET request to / should return 'Hello world!'", async () => {
const testClient = await superoak(app);
await testClient.get("/").expect("Hello world!");
});
We assume that the application is stored in a file called app.js
and the tests are stored in a file called app_test.js
that is -- for simplicity -- in the same folder with the app.js
file. Running the tests shows an error.
tree --dirsfirst
.
├── app.js
└── app_test.js
deno test --allow-all
Check file:///path-to-file/$deno$test.ts
running 1 tests
test GET request to / should return 'Hello world!' ... FAILED (19ms)
failures:
GET request to / should return 'Hello world!'
Error: expected 'Hello world!' response body, got 'Hello world'
// ...
failures:
GET request to / should return 'Hello world!'
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (19ms)
Although the test fails, we do not yet fix it at this point. Let's look into creating a project in GitHub, adding our source code to github, and enabling a continuous integration pipeline that will run our tests on GitHub whenever we push new code to GitHub.
Creating and adding a project to GitHub
GitHub is a service that provides hosting and versioning of source code with git. Previously, when deploying software to Heroku, we have already used git -- Heroku also uses git repositories for storing projects.
Creating a repository repository on GitHub is straightforward. Once you have logged in and are in the main page of your user, click the Repositories
tab. This will list your repositories. Then, click New
to create a new repository. This opens a page for creating a repository, which looks as follows -- in the example below, we have filled in the project name as wsd-deno-ci
and chosen that the project should be private (i.e. not visible to others).

When we click the create repository button, we are shown a page that has the repository details.
Now, on our computer, we navigate to the folder where we have the two files, i.e. app.js
and app_test.js
. In the folder, we initiate a git repository using the git init
command, add the files to the repository, set the branch as main, add the remote repository at github to the project, and push the contents of the project to github.
When we push the contents to github, GitHub prompts for authentication. After this, the files have been sent to github. Note that in the example below, the https://github.com/(username)/(repository-name).git
in git remote add origin https://github.com/(username)/(repository-name).git
should be changed to match your newly created repository.
tree --dirsfirst
.
├── app.js
└── app_test.js
0 directories, 2 files
git init
Initialised empty Git repository in /path-to-folder/.git/
git add .
git commit -m "initial commit"
[master (root-commit) hash] initial commit
// other info
2 files changed, 24 insertions(+)
create mode 100644 app.js
create mode 100644 app_test.js
git branch -M main
git remote add origin https://github.com/(username)/(repository-name).git
git push -u origin main
// credentials are asked, then files sent to github
Once the files have been sent to GitHub, they are visible on the repository page.

Authenticating with a key
The above example somewhat trivializes authentication. In practice, we recommend setting up two-factor authentication (2FA) for GitHub. Information on Authentication is available in GitHub Docs.
Continuous integration
When using GitHub, continuous integration is implemented using GitHub actions.
We wish to make sure that whenever code is added to the repository, tests for the project are run. Actions are added to the project through the Actions
tab. When we click the Actions
tab for the first time, we see a page that shows a list of available workflows and a link for setting a workflow ourselves.

We choose to set up a workflow ourselves.
Clicking the link set up a workflow yourself
leads to a page with an editor -- we are now editing the workflow related to an action associated with the project. A workflow outlines the steps that should be taken as a part of the action.
In the image below, we have changed the name of the file that we are editing to deno.yml
to indicate that we are creating a workflow for a deno project. The content of the file has not yet been adjusted.

Change the contents of the workflow file to the following.
name: Run tests on project
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup repo
uses: actions/checkout@v2
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: "v1.42.2"
- name: Run tests
run: deno test --allow-all
This effectively creates a workflow where on every push to the main branch on the server, the tests in the project are run. As the test environment, the latest ubuntu is used. For running the tests, Deno's version v1.42.2
is used.
Now, click the Start commit
button. This opens a page or a dialog asking for a commit message. The default is ok.
When the workflow (or action) has been created, let's go back to the main page. It should look something similar to the following one. Note in particular that there is a small orange dot next to the time when the commit was made (in the below picture, the commit was made 25 seconds ago).

When we wait for a while and reload the page, we notice that the orange dot has changed to a red cross. When we click that cross, we see a dialog that shows a message; the dialog also has a link Details
. When we click that link, we see a page that looks as follows. Running of the tests has failed.

In practice, we observe that running the tests failed. Clicking the failed option open allows us to see the actual error message from running the tests. As we already know, there is something wrong with our code that we need to fix.
Fixing the code
Fixing an issue with the code starts by retrieving the most up to date version of the project from the repository. This is done using the git pull
command. We see that a new file deno.yml
has been added to the folder workflows
under the folder .github
. There also exists a folder called .git
that contains the local git repository for the current project.
git pull
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 10 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (10/10), done.
From https://github.com/(username)/(repository-name)
b7787b9..e7deaea main -> origin/main
Updating b7787b9..e7deaea
Fast-forward
.github/workflows/deno.yml | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 .github/workflows/deno.yml
ls -a
./ ../ app.js app_test.js .git/ .github/
As the error message on GitHub indicated that the tests failed, we need to fix the application. Instead of responding with Hello world
to requests, the application should respond to requests with Hello world!
. The fix is shown below.
import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";
const app = new Application();
const router = new Router();
const hello = ({ response }) => {
response.body = "Hello world!";
};
router.get("/", hello);
app.use(router.routes());
export { app };
Once the error has been fixed, we can add the fixed file to github. We first check what files have been changed using the command git status
. Then, we add the changed file (in this case app.js
) using the command git add app.js
. Then, we add a commit message with the command git commit -m "(message)"
, where (message) corresponds to the message. Finally, we push the project to github using the command git push
.
git status
On branch main
Your branch is up-to-date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: app.js
no changes added to commit (use "git add" and/or "git commit -a")
git add app.js
git commit -m "Fixed output"
git push
// sending changes to github
Now, the changes have been sent to GitHub. When we open the repository page and click on the orange dot (or a green check, if the workflow has already been executed), we can see the results of running the workflow on the most recently pushed content. The image below shows what it looks like when all the steps have been completed succesfully.
