In this chapter, I continue the theme of TypeScript development tools started in Chapter 5, which introduced the TypeScript compiler. I show you the different ways that TypeScript code can be debugged, demonstrate the use of TypeScript and the linter, and explain how to set up unit testing for TypeScript code.
Preparing for This Chapter
For this chapter, I continue using the tools project created in Chapter 5. No changes are required for this chapter.
Tip
You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/Apress/essential-typescript .
Starting the Compiler
Debugging TypeScript Code
The TypeScript compiler does a good job of reporting syntax errors or problems with data types, but there will be times when you have code that compiles successfully but doesn’t execute in the way you expected. Using a debugger allows you to inspect the state of the application as it is executing and can reveal why problems occur. In the sections that follow, I show you how to debug a TypeScript application that is executed by Node.js. In Part 3, I show you how to debug TypeScript web applications.
Preparing for Debugging
Enabling Source Maps in the tsconfig.json File in the tools Folder
When the compiler next compiles the TypeScript files, it will also generate a map file, which has the map file extension, alongside the JavaScript files in the dist folder.
Adding Breakpoints
Adding the debugger Keyword in the index.ts File in the src Folder
There will be no change in the output when the code is executed because Node.js ignores the debugger keyword by default.
Using Visual Studio Code for Debugging
Most good code editors have some degree of support for debugging TypeScript and JavaScript code. In this section, I show you how to perform debugging with Visual Studio Code to give you an idea of the process. There may be different steps required if you use another editor, but the basic approach is likely to be similar.
To set up the configuration for debugging, select Add Configuration from the Debug menu and select Node.js from the list of environments when prompted, as shown in Figure 6-1.
Note
If selecting the Add Configuration menu doesn’t work, try selecting Start Debugging instead.
Changing the Code Path in the launch.json File in the .vscode Folder
The state of the application is displayed in the sidebar, showing the variables that are set at the point that execution was halted. A standard set of debugging features is available, including setting watches, stepping into and over statements, and resuming execution. The Debug Console window allows JavaScript statements to be executed in the context of the application so that entering a variable name and pressing Return, for example, will return the value assigned to that variable.
Using the Integrated Node.js Debugger
Node.js provides a basic integrated debugger. Open a new command prompt and use it to run the command shown in Listing 6-5 in the tools folder.
Note
There are no hyphens before the inspect argument in Listing 6-5. Using hyphens enables the remote debugger described in the following section.
Starting the Node.js Debugger
Continuing Execution
Evaluating an Expression in the Node.js Debugger
Type help and press Return to see a list of commands. Press Control+C twice to end the debugging session and return to the regular command prompt.
Using the Remote Node.js Debugging Feature
Starting Node.js in Remote Debugger Mode
Using the TypeScript Linter
A linter is a tool that checks code files using a set of rules that describe problems that cause confusion, produce unexpected results, or reduce the readability of the code. The standard linter for TypeScript is TSLint. To add TSLint to the project, use a command prompt to run the command shown in Listing 6-9 in the tools folder.
Note
At the time of writing, the TSLint team has announced they will merge the TSLint functionality into ESLint, which is a popular JavaScript linter. An easy migration path has been promised once ESLint becomes capable of linting TypeScript.
Adding a Package to the Example Project
The Contents of the tslint.json File in the tools Folder
The TSLint Preconfigured Rule Sets
Name | Description |
---|---|
tslint:recommended | This is the set of rules suggested by the TSLint development team and is intended for general TypeScript development. |
tslint:latest | This set extends the recommended set to include recently defined rules. |
tslint:all | This set contains all of the linter’s rules, which can produce a large number of linting errors. |
The linterOptions settings in Listing 6-10 select the verbose output format, which includes the name of the rules in the error messages, which is important when you first start using a linter and need to tailor the linting settings.
Running the TypeScript Linter
The linter uses the tsconfig.json file to locate the TypeScript code files and checks them for compliance with the rules in the recommended set. The code in the example project breaks four of the linter’s rules: the eofline rule requires a newline at the end of a code file, the no-debugger rule prevents the debugger keyword from being used, the no-console rule prevents the console object from being used, and the prefer-const keyword requires the const keyword to be used in place of let when the value assigned to a variable isn’t changed.
Disabling Linting Rules
The problem is that the value of a linting rule is often a matter of personal style and preference, and even when the rule is useful, it isn’t always helpful in every situation. Linting works best when you only get warnings that you want to address. If you receive a list of warnings that you don’t care about, then there is a good chance you won’t pay attention when something important is reported.
Of the four rules that are broken by the code in the example project, two of them report issues that I do not consider a problem. I use the console object to write messages as a simple debugging tool, and it is a useful feature for writing book examples. Similarly, I don’t terminate my code files with a new line because many of my code files are used for book examples and a final newline doesn’t fit with the template that I use to write chapters.
The prefer-const rule falls into a different category: it highlights a deficiency in my coding style that I have learned to accept. I know that I should use const instead of let, and that’s what I try to do. But my coding habits are deeply ingrained, and my view is that some problems are not worth fixing, especially since doing so requires breaking my concentration on the larger flow of the code I write. I accept my imperfections and know that I will continue to use let, even when I know that const would be a better choice.
Disabling a Linting Rule in the tslint.json File in the tools Folder
The rules configuration section is populated with the names of the rules and a value of true or false to enable or disable the rules. Some rules can be configured to alter their behavior, such as setting the level of indentation enforced by the linter, for example, but all rules can be switched off for a project using false.
Some rules are useful in a project but disabled for specific files or statements. This is the category into which the no-debugger rule falls. As a general principle, the debugger keyword should not be left in code files, just in case it causes problems during code execution. However, when investigating a problem, debugger is a useful way to reliably take control of the execution of the application, as demonstrated earlier in this chapter.
Disabling a Linter Rule for a Single Statement in the index.ts File in the src Folder
The comment in Listing 6-13 tells the linter not to apply the no-debugger rule to the next code statement.
Tip
Rules can also be disabled for all the statements that follow a comment that starts with tslint:disable. You can disable all linting rules by using the tslint:disable or tslint:disable-next-line comment without any rule names.
The Joy And Misery Of Linting
Linters can be a powerful tool for good, especially in a development team with mixed levels of skill and experience. Linters can detect common problems and subtle errors that lead to unexpected behavior or long-term maintenance issues. I like this kind of linting, and I like to run my code through the linting process after I have completed a major application feature or before I commit my code into version control.
But linters can also be a tool of division and strife. In addition to detecting coding errors, linters can be used to enforce rules about indentation, brace placement, the use of semicolons and spaces, and dozens of other style issues. Most developers have style preferences that they adhere to and believe that everyone else should, too. I certainly do: I like four spaces for indentation, and I like opening braces to be on the same line and the expression they relate to. I know that these are part of the “one true way” of writing code, and the fact that other programmers prefer two spaces, for example, has been a source of quiet amazement to me since I first started writing code.
Linters allow people with strong views about formatting to enforce them on others, generally under the banner of being “opinionated.” The logic is that developers spend too much time arguing about different coding styles, and everyone is better off being forced to write in the same way. My experience is that developers will just find something else to argue about and that forcing a code style is often just an excuse to make one person’s preferences mandatory for an entire development team.
I often help readers when they can’t get book examples working (my e-mail address is [email protected] if you need help), and I see all sorts of coding style every week. I know, deep in my heart, that anyone who doesn’t follow my personal coding preferences is just plain wrong. But rather than forcing them to code my way, I get my code editor to reformat the code, which is a feature that every capable editor provides.
My advice is to use linting sparingly and focus on the issues that will cause real problems. Leave formatting decisions to the individuals and rely on code editor reformatting when you need to read code written by a team member who has different preferences.
Unit Testing TypeScript
Some unit test frameworks provide support for TypeScript, although that isn’t as useful as it may sound. Supporting TypeScript for unit testing means allowing tests to be defined in TypeScript files and, sometimes, automatically compiling the TypeScript code before it is tested. Unit tests are performed by executing small parts of an application, and that can be done only with JavaScript since the JavaScript runtime environments have no knowledge of TypeScript features. The result is that unit testing cannot be used to test TypeScript features, which are solely enforced by the TypeScript compiler.
Adding Packages to the Project
The jest package contains the testing framework. The @types/jest package contains the type definitions for the Jest API, which means that tests can be written in TypeScript. The ts-jest package is a plugin to the Jest framework and is responsible for compiling TypeScript files before tests are applied.
Deciding Whether To Unit Test
Unit testing is a contentious topic. This section assumes you do want to do unit testing and shows you how to set up the tools and apply them to TypeScript. It isn’t an introduction to unit testing, and I make no effort to persuade skeptical readers that unit testing is worthwhile. If would like an introduction to unit testing, then there is a good article here: https://en.wikipedia.org/wiki/Unit_testing .
I like unit testing, and I use it in my own projects—but not all of them and not as consistently as you might expect. I tend to focus on writing unit tests for features and functions that I know will be hard to write and that are likely to be the source of bugs in deployment. In these situations, unit testing helps structure my thoughts about how to best implement what I need. I find that just thinking about what I need to test helps produce ideas about potential problems, and that’s before I start dealing with actual bugs and defects.
That said, unit testing is a tool and not a religion, and only you know how much testing you require. If you don’t find unit testing useful or if you have a different methodology that suits you better, then don’t feel you need to unit test just because it is fashionable. (However, if you don’t have a better methodology and you are not testing at all, then you are probably letting users find your bugs, which is rarely ideal.)
Configuring the Test Framework
The Contents of the jest.config.js File in the tools Folder
The roots setting is used to specify the location of the code files and unit tests. The transform property is used to tell Jest that files with the ts and tsx file extension should be processed with the ts-jest package, which ensures that changes to the code are reflected in tests without needing to explicitly start the compiler. (TSX files are described in Chapter 14.)
Creating Unit Tests
The Contents of the calc.test.ts File in the src Folder
Useful Jest Matcher Functions
Name | Description |
---|---|
toBe(value) | This method asserts that a result is the same as the specified value (but need not be the same object). |
toEqual(object) | This method asserts that a result is the same object as the specified value. |
toMatch(regexp) | This method asserts that a result matches the specified regular expression. |
toBeDefined() | This method asserts that the result has been defined. |
toBeUndefined() | This method asserts that the result has not been defined. |
toBeNull() | This method asserts that the result is null. |
toBeTruthy() | This method asserts that the result is truthy. |
toBeFalsy() | This method asserts that the result is falsy. |
toContain(substring) | This method asserts that the result contains the specified substring. |
toBeLessThan(value) | This method asserts that the result is less than the specified value. |
toBeGreaterThan(value) | This method asserts that the result is more than the specified value. |
Starting the Test Framework
Starting the Unit Test Framework in Watch Mode
Making a Test Fail in the calc.ts File in the src Folder
Changing a Unit Test in the calc.test.ts File in the src Folder
Summary
In this chapter, I introduced three tools that are often used to support TypeScript development. The Node.js debugger is a useful way to inspect the state of applications as they are being executed, the linter helps avoid common coding errors that are not detected by the compiler but that cause problems nonetheless, and the unit test framework is used to confirm that code behaves as expected. In the next chapter, I start describing TypeScript features in depth, starting with static type checking.