How to Setup a Basic Test-Driven Typescript Environment

Fast and easy instructions for new developers

How to Setup a Basic Test-Driven Typescript Environment
Photo by Ferenc Almasi on Unsplash

If you’re starting out learning Typescript, then the implications to project structure are just as daunting as (if not more so) than cleaning up your JS code to be type-safe.

Moreover, if you’re looking to publish your project to NPM then there are even more considerations with how you setup your development space. Since one of the main reasons to use Typescript is to produce safer code, it’s also a great time to throw in automated testing… but isn’t that overwhelming?

I thought so at first, but what I discovered is it’s really not and it actually accelerated my development. So, in this tutorial we’re going to be starting up a Typescript project, then setting up automatic building and testing so you can develop freely all within a single project directory.

Setting up the Typescript Project

In this section we’re going to initialize a NodeJS project then setup Typescript.

We’ll assume you’re already in your project directory whether that means you cloned an empty GitHub repo, locally ran git init, or simply made a truly barebones directory.

To create a new NodeJS project run the command:

npm init --y

This is going to initialize the project and the --y will simply auto-answer yes to all prompts. We’re now able to manage packages, which is good because installing one is going to be the next step. Run the command:

npm install --save-dev typescript

This will add the typescript library as a dev dependency and also install it in your node_modules directory. Finally, let’s initialize our Typescript project with the following command:

npm run tsc --init

If you run into an error that tsc is not a command, you may need to install typescript globally with the command

npm install -g typescript

You’ll see a file named tsconfig.json was made. If you look inside it’s a bit overwhelming, but essentially it’s all the different configurations possible, commented out except the essentials.

The most important distinction between TS and JS as a new learner is that TS code needs to be built/compiled into JS before it is run

If you’re used to writing JS, you’re probably comfortable saving and testing, but with TS you have to run the tsc build command to create the JS code that will then be run. Let’s automate this step.

Modify the scripts key in your package.json file so it looks like this:

"scripts": { 
    "start": "tsc --watch", 
},

Afterwards, run the command npm start and you basically don’t need to worry about building TS anymore while developing because watch mode is going to automatically build each time you save.

To test this out, let’s create a file named index.ts in our root directory, define a simple Typescript function, and export it.

export function echoString(msg: string): string { 
    return msg; 
}

When you save, you should see an index.js file created with vanilla JS code. This is what will actually be run, but if you make another modification to index.ts you’ll see the compilation happens automatically.

Setting up Automated Testing

Now that we’re ready on the TS side, let’s get to the test part of “Test-driven Typescript environment.” We’re going to use Jest, which is a popular Javascript testing framework.

Start by installing Jest as a dev dependency just like Typescript:

npm install --save-dev jest

Now, we’re going to make an important modification to your package.json so that jest can run in a similarly automated mode as tsc.

"scripts": { 
  "start": "tsc --watch", 
  "test": "jest --watchAll" 
},

After saving, in a second terminal run the command npm test where you’ll be prompted to specify the way in which you want jest to watch your changes. I like to choose o which will only run tests related to changed files.

Now, create a tests directory and inside, create a file named echoString.test.js with the following content:

const { echoString } = require("../"); 
 
describe("Test calling echoString()", () => { 
  it("should not throw an error", () => { 
    expect(echoString("hello")).toBe("hello"); 
  }); 
 
});

What we’re doing here is importing echoString, describing a set of tests, and adding one test that declares calling the function should correctly return the supplied string.

If you already have jest in watch mode, when you save this file the test should automatically run and pass.

The Development Workflow

At this point the tutorial part is done. You’ve set up a Typescript project, added testing, and both are set to run automatically. This means when you save a TS file, tsc will automatically build and jest as a result will automatically test.

So, how should you progress? Here’s what I recommend:

  1. Write a test and save
  2. See that the test fails
  3. Write TS code until it doesn’t fail
  4. Rinse and repeat

I mentioned at the top of the article that this accelerated my process. It may not feel like it at first, but consider this:

  • When you create a new variable/function/etc, it’s top-of-mind and you know exactly what you should and shouldn’t expect — write the tests now!
  • If you write your tests as an afterthought, you’re trying to recall and think of all the positive/negative cases…it’s not fresh in your mind anymore.
  • Even worse, you don’t catch all the cases and later on down the line you start hitting errors. Now you have to familiarize yourself to the code and find the proverbial needle in the haystack.

Once you get in the routine of “write a test, fix the test” you’ll enter a much more methodical, deliberate development flow.

I saved this for the end so that the tutorial didn’t get too long, but I would highly recommend making a few modifications when setting up the Typescript project to keep things more organized.

  1. Add a directory to hold the JS that is compiled. I call this directory dist normally.
  2. Modify your tsconfig.json file by uncommenting the outDir line and changing the value to ./dist or whatever you called the directory.
  3. Since you moved where the built JS code lives, update the import in your test file(s) to require("../dist")

This small step isn’t required, but it prevents your root directory from getting overly cluttered.


How has test-driven development worked for you? How about learning Typescript? Share your experiences, thoughts, and questions in the comments and happy coding!

Subscribe to Dreams of Fortunes and Cookies

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe