Introduction to TypeScript with unit testing and continuous integration
In this blog post, I'll introduce you to TypeScript, a superset of JavaScript that has helped me write more robust and maintainable code. I'll also share with you how I use unit testing and continuous integration with TypeScript to build high-quality software.
TypeScript introduces static typing and other powerful features that can make your code more reliable and easier to maintain, especially in larger projects. With TypeScript, I can catch errors early and benefit from better tooling support.
But testing your code is just as important as writing it, and that's where unit testing comes in. I'll show you how I use Jest, a popular testing framework for JavaScript, to write unit tests for my TypeScript code. You'll learn how to write effective tests and catch bugs before they cause problems.
And finally, I'll show you how to set up continuous integration using GitHub Actions. By automating the building, testing, and deployment of your code, you can catch issues early and ship software faster. I'll guide you through the process of setting up a CI pipeline, so you can test your code automatically and get fast feedback on your changes.
You can find a GitHub repository containing the below mentioned files here: jonasclaes/2023-tutorial-build-test-deploy
Whether you're new to TypeScript, unit testing, or continuous integration, this guide has something for everyone. I'm excited to share my knowledge and help you take your JavaScript development skills to the next level! So let's get started.
Project initialization
To get started, the first thing I did was create a new folder for my project. I then initialized an empty JavaScript project by running the command yarn init
inside this folder. I also created a src
folder to contain the source code for my project.
Next, I installed TypeScript and the Node.js types as development dependencies by running the command yarn add -D typescript @types/node
. This would allow me to use TypeScript in my project and ensure that the code I write is compatible with Node.js.
Once the dependencies were installed, I created a new file called tsconfig.json
in the root of my project directory with the following contents:
This is an optimal configuration for the small project we will be creating. It sets the module system to NodeNext
which allows us to use the latest features of Node.js. It also specifies the target environment to be ES2020
, enables source maps, and sets the output directory for the compiled code to dist
. Finally, it enables the esModuleInterop
flag which allows us to use modules that were not designed for TypeScript.
In the package.json
file, I added "type": "module"
to indicate to Node.js that the code we will be compiling is an ES module. This is important because it enables the use of ES module syntax such as import
and export
.
Finally, I added a script called build
to the package.json
file which runs the TypeScript compiler (tsc
) to compile the TypeScript code to JavaScript. I have also added a script called start
to the package.json
which just runs our compiled code using Node.js. Your package.json
file should look something like the following:
With this project initialization out of the way, we're ready to start writing TypeScript code and testing it with unit tests.
Unit testing
Unit testing is an essential part of software development that is often overlooked or misunderstood. In simple terms, unit tests are tests that are designed to test small, individual units of code, such as functions or methods. These units should be able to function on their own and have no side effects. It's important to note that unit tests should not connect to databases, interact with other services, or depend on other code.
To get started with unit testing in our project, I first added Jest and the corresponding types by running the following command: yarn add -D jest ts-jest @types/jest
. Jest is a popular testing framework for JavaScript, and ts-jest is a Jest preprocessor that allows us to use TypeScript with Jest. I also added a test
script to the package.json
which invokes jest
. Your package.json
should look something like the following:
Next, I generated a jest.config.js
file by running ts-jest config:init
. However, we needed to rename the jest.config.js
file to jest.config.cjs
because TypeScript needs to know what kind of JS file this is. I also created a folder called test
which will contain all of our unit tests. The jest.config.cjs
file should look something like the following:
Now that our test environment is set up, we can start writing our tests. To begin, I added a small piece of code to src/index.ts
which we can test later on. This code exports a function called sum
, which takes in two numbers and returns their sum. Here's the code:
After adding this code, I then created a test file at test/index.test.ts
. In this file, I imported the sum
function from src/index.ts
and created three small unit tests to verify that it works correctly. Each test uses Jest's expect
function to compare the output of the sum
function to an expected value using the toBe
matcher. Here's the test code:
These tests check that sum
works correctly for different input values, ensuring that it can correctly add two numbers.
Finally, I ran yarn test
to run the unit tests. If everything went okay, you should see a "PASS" message for all 3 tests in the console. By writing and running these tests, we can be more confident that our code is working correctly, and can detect and fix issues more easily as we continue to develop our project.
Automated testing
GitHub Actions is a way to automate tasks and processes that happen when you interact with your GitHub repository. In this case, we want to create a workflow to automatically test and deploy our code whenever we push new changes to the repository.
First, we create a file called test-and-deploy.yml
in the .github/workflows
directory. The contents of this file are written in YAML, a markup language that is often used for configuration files. This file should look something like the following:
The first line of the file specifies the name of the workflow: "Test and deploy". The on
section specifies when this workflow should be triggered. In this case, we want it to be triggered whenever we push new changes to the repository.
The next section defines the Test
job. A job is a series of steps that should be executed. In this case, we want to run our tests. The runs-on
field specifies which operating system we want to run our tests on. In this case, we're using the latest version of Ubuntu.
The steps
field is an array of steps that should be executed. The first step checks out the code from the repository. The second step sets up the environment by installing Node.js and caching our dependencies using Yarn. The third step installs the dependencies. The fourth step runs our tests.
The next section defines the Deploy
job. This job should only run if the Test
job succeeds (indicated by the needs
field). The steps are similar to the Test
job, with a few additions. First, we compile our source code using the yarn build
command. Then, we create a zip file of the compiled source code. We use the actions/github-script
action to get the latest release of our code, so that we can create a new release with the updated code. We create a new release using the actions/create-release
action and upload the compiled source code using the actions/upload-release-asset
action.
When you commit and push this file to GitHub, you should see a new action starting to run in the "Actions" tab of your repository. This action will run the Test
job and then the Deploy
job, if the Test
job succeeds. This means that your code will be automatically tested and deployed whenever you push new changes to the repository.
Conclusion
In conclusion, automated testing is an important part of software development that ensures the code is functioning as expected and catches any errors early on in the development process. Using a CI pipeline like GitHub Actions makes it easy to automate this process, so that each time code is pushed to the repository, the tests are run automatically. This can save a lot of time and effort in the long run, as any issues can be caught and addressed before they become bigger problems. By following the steps outlined in this tutorial, you should now have a better understanding of how to set up automated testing with Jest and GitHub Actions, and how to ensure that your code is functioning as expected before it gets deployed to production.