Recently I've been getting back into doing some algorithms coding challenge problems, and I really wanted to setup for myself a nice, comfortable coding environment that I could be proud of and that really followed the TDD principles of Uncle Ben, Ken Beck, and all the other gurus. Anyway, this is a guide for setting up simple, barebones TypeScript node.js projects for TDD. I'll show you how to set up a brand new node project, how to make your project a "typescript project", how to add mocha and chai in watch mode, and finally how to see your test results in a nice code coverage report. It's a lot I know, so let's dive into it!
I'm going to assume you're using a mac terminal or at least some type of unix-based shell. Now, let's make a completely new, fresh project- no scaffolding, no starter boilerplate crutches- just a straight up empty folder!
I can't gaurentee the commands here will work for you if you are on a different node verison. At the time of this writing I'm using node v10.16.0, but any v10 version of node should be fine. I normally like to adjust my node version with nvm like so:
Ok, now navigate into your empty project folder, and let's make this thing a node project! We''ll create a package.json file with the npm init command. You can pretty much just keep hitting enter and accept the defaults for the questions it asks as you can always go in and change things in you package.json later.
Now, let's turn this plain jane node project into a typescript project! First, let's install typescript globally as this will give us access to the typescript command line tool, tsc.
Now we just need to make some updates to the package.json file. Add these two commands in the "scripts" section for start and test. In this case npm start runs the tests in "watch mode" so that the tests are rerun after any change of the code while npm test does a single run of the tests and produces code coverage reports (since we use using both "text-summary" and "html" then it will output the coverage results to the console and create an entire little website displaying the code coverage! The big block of config settings under "nyc" controls how nyc runs your tests files. Here we are basically just telling it to supports typescript and specify what resporter(s) we want it to use.
Ok, now that all the boring setup that needs to be done in order to get this TDD show on the road in out of the way, let's make a simple class! Here I'll name the class something generic but similar to how I've been naming previous ones. How about ChallengeProblemSolver? For classes that are doing some calculation or providing some helper function I like to name them with an -er suffix. To me, this makes sense. The functions are the things that are ultimate doing the work, and the class is a container. It's a noun, a thing, even you could think of the class as a person if you really wanted to personify it. The class is the container of one or a few functions, and I think also naming the class this way enforces the "single responsibility rule". Your classes should each be doing one thing and doing that thing well, and so it shouldn't be very difficult for each class to say, "this is what I do. This is who I am, and this is my name".
The class above exposes one public function, solve problem, which takes an array of numbers and returns the integer 4. I'm not sure what problem is being solved here that the answer is always 4, but this is a great example of a simple TypeScript class with a single function.
Now comes the fun part, writing tests for the class! Let's create a new file called ChallengerProblemSolver.test.ts and put it next to the other files. We'll import our class that we want to test, import "expect" so that we have it to use in our assertions, and then make the overarching describe block for our tests of this solveProblem function. We declare a variable called challengeProblemSolver outside of our beforeEach or it blocks purposely so that it can be used across all of them. Before each it block the beforeEach is run thus creating a fresh instance of ChallengeProblemSolver to be used in each test. Then inside of the it block we invoke the function and use the "expect" keyword to assert that the result is the expected outcome.
So now we have a class, a test for it, and all the npm setup things out of the way, awesome! Now all that's left to do is run the tests and get to TDD developing!
Notice how there are two scripts in my package.json file above: start and test. These are pretty common npm commands, but the way that I'm using them here is a bit unusual. Let's take a closer look at them. When you run npm start then that will trigger this command:
So what's happening in the above command? Well, first we are running "ts-mocha", basically mocha but for typscript. We are passing it the -p flag, short for project, which takes our tsconfig.json file as an argument, and then finally we are telling it run any file anywhere within the current directory that ends in ".test.ts". We are then adding the -w flag for "watch" to make the tests rerun automatically when the source code or test code is changed (essential for tdd!), and we also add the "--watch-extensions ts" to make sure the watch mode works with our typescript files. Note that you could also install ts-mocha globally and avoid having to drill into the node_modules, but this way in my experience seems to cause less troubles. Now, let's take a look at the command for npm test:
In this above command we are using "nyc" along with mocha to run the tests and generate a code coverage report. We pass in one argument to denote that any file in the current directory that ends in .test.ts should be treated as a test file. Back when we created the package.json file we added a block for "nyc", and that is where we specify that we are using typescript and control a few other settings.
Writing good unit tests can seem tricky at first, but it is something that anyone can learn. Practice, practice, practice, and over time you will start to see patterns and discovers things that work or don't work for you. TypeScript and testing can be frustrating things to set up correctly and will give your even more things to debug! For some individuals / teams the fear not being able to write tests or not benefitting enough from having tests is so strong that it prevents them from even giving it a chance at all! The client, product owner, and end users do not care at all about neither typescript nor unit testing, but that doesn't mean they aren't important! These are things that never make it to the visible surface but that help you to build better things faster. Unit tests are there solely for you, the developer. They are there to give you confidence that your functions actually work the way you intended them to, and they provide a nearly instantaneous means of reaffirming that confidence at any time in the future. They are there to expose the bugs you would have otherwise had, and do prevent them from appearing after the addition of new features. Tests and TypeScript are there to make you life better, and you should embrace them. I hope this guide will help you get over the hurdles of just setting up a development environment so that you focus on actually harness this test-driven workflow to build some amazing new software.
The posts on this site are written and maintained by Jim Lynch. About Jim...