Running unit tests
This section will tackle our very first task: running unit tests.
Constructing workflows
The most fundamental questions that Github Actions workflows answers are:
When will it run?
What will it do?
"When will it run?" is answered by specifying the events that the workflow responds to. In our scenario, we want this workflow to run when a pull request is created on the repository.
"What will it do?" can be then broken down into several more guiding questions?
Which OS should this run in?
Are there key differences between OSes that should be accounted for?
What programming language is this project written in?
What steps should be run to achieve the given workflow?
Can certain tasks be split into separate jobs and run in parallel?
What are jobs that depend on others?
We will take a look at how we can answer questions (4) and (5) in a following section. Let's first tackle questions (1), (2), and (3) in our scenario.
In the case of
calculator.test.ts
, the OS we run it on does/should not matter as there are no OS specific test cases. For simplicity, we will pick Ubuntu.The project is written in React, so it depends on having Node.js available.
We first need to get the repository, install all necessary dependencies, and run the
test
script that is provided (seepackage.json
)
You might be asking yourself, "What if I have unit tests that are specific to an OS? Or version of Node.js? Or Ubuntu version?" Github Actions supports something it calls matrix strategies, that run a given workflow across a matrix of variables. We will briefly dive into it in the Cookbook!
Designing the workflow
Now that we have clearly outlined the key details of this workflow, let's get down to writing your very first workflow file. Remember, all Github Actions workflows must reside in .github/workflows
, so create a new file called ci.yml
!
Breaking it down
Let's break down each section and explain what we're doing.
Workflow name
We are giving the workflow a name that Github Actions will display. If the name is omitted, GitHub displays the workflow file path relative to the root of the repository.
Workflow triggers
Then, we specify the times where this workflow runs, aka "when will it run?". They are specified with the on
key and the values correspond to the list of events that trigger a workflow.
In our case, we know that we want to run the workflow during a pull request, so we have included the pull_request
event. We have also included the workflow_dispatch
event, so that we are able to manually trigger this workflow from Github without requiring a pull request (more about it here). This is particularly useful if we want to verify that the workflow works without going through the hassle of creating a pull request.
Defining jobs
We then start to specify the jobs that comprise the workflow. We give the job a job ID of unit-tests
.
Recall that we said that every job runs in its own runner, which is a virtual machine by default. Therefore, we need to specify the OS that our unit-tests
job will execute in, which we have decided earlier to be Ubuntu. As we are not particular about the version of Ubuntu we will use, we can use the ubuntu-latest
, which is one of the many available Github-hosted runners.
This means that we can think of every step of this job executing within an Ubuntu virtual machine (because they really do!). So we will be using bash
commands, and we will have access to things like the apt
package manager that is available in Ubuntu.
Fetching the current repository
We declare the steps of a job under the steps
array as a list of dictionaries. As we are running the job in a virtual machine, we will basically have an empty machine at the start of the job, and it is our responsibility to start populating and interacting with this empty machine to create the intended workflow.
The very first step we need to do is fetch the current repository, so that the virtual machine runner has access to the project structure, and more importantly, the unit tests. We give the step a human readable name (that is also displayed on Github) using the name
key. If there is no name provided, Github will display the script or action that is being used.
Here, we use an action — which are reusable extensions that perform some set of operations — called actions/checkout@v4
. You can read more about what the action is comprised of here, but essentially, we are using it to perform a checkout of the current repository, retrieving all of its contents (from the latest commit) onto the virtual machine runner.
Setting up Node.js
Then, we need to setup Node.js on the virtual machine runner. We can use the action actions/setup-node@v4
to setup Node.js automatically for us. You can read more about the action here.
We can specify inputs for the action using the with
key, providing the various inputs as a dictionary. In this case, we want to use Node.js version 20, and we want to use the yarn
package manager — instead of npm
— as that is what we have used for the example application.
Installing dependencies
Before we can run the unit tests, we need to ensure that all of the necessary project dependencies are retrieved. This is where we can use scripts in steps.
Recall in Defining jobs, we mention that all steps in the job are effectively running on Ubuntu, so we will specify scripts that use Ubuntu's built-in shell: bash
. You might want to specify a different shell for certain use cases, which you can do so here.
So, we declare our script through the run
string. The pipe operator after the run
key indicates that we are specifying a multi-line string in YAML.
The script we will run this time is yarn
, which effectively installs the project's dependencies. You can think of this as running yarn
directly in an Ubuntu terminal, where the current folder ($GITHUB_WORKSPACE
) is the root of the Node.js project.
Running unit tests
Finally, we can start to execute the unit tests of the project. Again, we use a script, but this time, the script will be NODE_ENV=production yarn test
which effectively sets the environment variable NODE_ENV
to be of value production
.
test
is a script that we have provided in the example application, and it essentially runs vitest
, executing calculator.test.ts
.
Voilà ! You have successfully written your very first Github Actions workflow! Simple isn't it? Let's recap what we did.
Visualizing the workflow
At a glance, this is the high-level overview of the new CI/CD pipeline you have written.
To properly visualize and understand how the filesystem of the virtual machine runner changes throughout the job, we have also created this visualization (bolded text are the changes between steps):
Verifying the workflow
Manually running workflows
Once you have added the workflow, you need to commit and push it!
Then, we can start to verify that the Github Action works as intended! This is where the workflow_dispatch
event comes in handy, where we are able to manually trigger Github Actions. If you navigate to your fork of the example repository, you can visit the Actions tab. You will see the following:
It lists the workflows available, and what has run/are running. We are interested in our new pipeline CI/CD Pipeline
, so select it and you should see the following:
It looks almost the same, but this time, there is a banner that tells you that "This workflow has a workflow_dispatch
event trigger". Then, there is a dropdown to "Run workflow", select it and stick with main
and click the "Run workflow" button:
Refresh the page, and you will now see a new entry in the table:
Give it a few seconds and then click into the action. You will see that the unit tests have failed:
This is because we have intentionally made one of the unit tests to fail (divide ½ is not 0.4!). Let's try to fix this unit test while exploring the the pull_request
event!
pull_request event
The following steps expects some level of understanding of Git. You can refer to our Git guide for more information!
This guide is not a software engineering exercise, so we will tell you exactly where the error is and we will focus on examplifying the pull_request
event.
We had intentionally set one of the unit test assertions to be incorrect, specifically along these lines:
We have set the expected value to be 0.4
when it should clearly be 0.5
! Let's fix this as a pull request to your own repository to see the pull_request
event in action.
Create a new branch, called fix-unit-test
:
Then, go to the file calculator.test.ts
and fix line 13 to be the following:
Then, add the file and create a commit. The commit message can be anything you want:
Finally, push the branch to your fork:
Then, go to your fork on Github and create a new pull request. Pull request > New pull request. Give the pull request any title and you can leave the description blank.
The base branch should be YOUR main
branch, not the original repository's branch!
Create the pull request and wait a while. You will notice that the component towards the bottom changes to this:
This is how you know that your workflow is running and it was triggered by the pull_request
!
Now, if you select the CI/CD pipeline running, you will be brought back to the same page as earlier, instead, this time, you will notice that the workflow passes!
In fact, you will even see the individual steps of the job unit-tests
that you defined! Wonderful! You have successfully:
Created a new Github Actions workflow
Observed how a failing unit test might look like
Fixed and verified that the
pull_request
event is working
Go ahead and merge the pull request into main
and update your local repository to receive the latest changes:
Now that we have implemented the very first step, let's take a look at implementing step 2: linting!
Last updated