How to Unit Test jscodeshift Transforms With ts-jest

Create tests for jscodeshift transforms for a faster feedback loop

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Practical Abstract Syntax Trees course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Practical Abstract Syntax Trees, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Practical Abstract Syntax Trees
  • [00:00 - 00:49] When working on a transform, it likely only needs to be run once on a codebase, unless it's intended to be reused multiple times. For example, the React CodeMod package is a set of transforms to help update React APIs across versions. The React CodeMod transforms are a good example of when testing can be helpful, since they'll be run on many codebases. Even if a transform is only intended to be run once on your codebase, writing tests can be a good way to streamline development. The process we've been using up to this point has been to work on the transform, run it on the codebase, undo the changes using getReset and repeat. The test helpers from JS codeshift can help turn this into a more automated test-driven development process, and make the iteration much faster by removing the need to run it on the codebase and undo the changes.

    [00:50 - 00:58] Let's continue with the same JS codeshift transform from the previous lesson. We can step by creating two new directories for the test cases and test fixtures.

    [00:59 - 01:16] The JS codeshift test utilities require these exact directory names. They also require the types be written with Jest, a popular JavaScript testing framework. The test can be written in JavaScript or TypeScript, but since the transforms written in TypeScript, it makes sense to also write the tests in TypeScript.

    [01:17 - 03:42] This requires some extra setup with either Babel or TS Jest. There are trade-offs with either approach, but the Babel approach only transpiles and doesn't perform any type checking. The TS Jest package is a bit easier to set up and also performs type checking. Either approach would work, but let's install the necessary dependencies to get TS Jest working. We can then initialize TS J est. Now with the test tooling setup, it's time to add a test. Create a new transform-test.ts file in the test directory. We can then import the define test helper from the JS codeshift test utils. The JS codeshift types don 't declare the define test helper, so using a require instead of an import will work around any of those type issues. The define test helper takes several arguments. The first argument is the current directory. This is used to automatically load the transform in test fixtures. The second argument is the name of the transform's file name, excluding the extension. In this case, it's named transform. The third argument is any options for the transform. We don't have any options, so we can pass an all. The fourth argument is the name of the test fixtures. This will be suffixed with either input or output, including the extension. For now, let's call it basic. In filing the last argument, our JS codeshift options will set the parser to Babel on. In this case, we'll have one test file because there's one transform. However, the test fixtures will contain input and output pairings, and there can be as many as needed. The test currently expects the test fixtures with the name basic, suffix with dot input and dot output, along with the extension which will be dot JS in this case. Next, let's add these two files and test fixtures. The input will be what is passed into the transform, and the define test helper will assert the transform output matches the test fixture output. For the basic input, we defined a button element with the button class name in the secondary class variant. For the output, we've included the button import, along with the transform button component in variant prop. Next, we can update the test script in package dot JSON to instead run the just package. Now , back on our terminal, we can run npm test and pass the watch options to just. We can see that the test passed. Let's try updating the input to have a different value.

    [03:43 - 05:55] We can now see that the test fails, since we changed the input and didn't update the output to reflect that. This basic test case confirms that the transform works for converting the secondary button into the equivalent component, but there are a number of other cases missing. Let's add three more test cases. One, testing props with all default values. One, testing props with no default values, so all the props, and finally a more complex transformation. We can then create these six new files. For the default input case, we define props on the button element that are the default values for the button component. The output therefore doesn't require any props. The all props case is the opposite of the default case, where all props are defined with non-default values. The output therefore requires all props passed with a value. The complex case is done to verify that custom button elements that don't have the button class name aren't transformed. It also verifies it can transform nested elements. We can switch back to the terminal to see that all the tests are passing. For the most part, these four test cases cover most of the logic and edge cases of this transform. In practice, this test-driven approach to creating a transform can be helpful for large complex transforms. An approach like this can work well when working on custom transforms. First, define some test cases, then updating the transform until it pass all those tests, then run it on the codebase, and if there are any new issues go back to step one and redefine those test cases, otherwise the transform is complete. When running a quick one -off transform, it might not be necessary to create tests for the transform itself, since the changed code can be reviewed and verified once. Now that the code has been fully migrated to the new button component, it would be ideal to prevent anyone adding button elements in the future instead of button components. The next module will explore how to ensure the codebase remains up-to-date.