Betterer v1.0.0
Tags: improvement, incremental, refactoring, testing
Published: 2020-05-14 17:20:34

I'm stoked to announce [v1.0.0](https://www.npmjs.com/org/betterer) of [**Betterer**!](https://github.com/phenomnomnominal/betterer)

I've been locked down in New Zealand for the last little while, and I've used some of that time to smash out what I think is a pretty compelling v1 release of a tool that I'm really excited about!

## What is **Betterer**?

[**Betterer**](https://github.com/phenomnomnominal/betterer) is a test runner that helps make incremental improvements to your code! It is based upon [**Jest**'s snapshot testing](https://jestjs.io/docs/en/snapshot-testing), but with a twist...

I'm sure many of us have been in situations where we've seen big changes we'd like to make, or new standards or design decisions that we'd like to encourage, but we just don't have the time to do it.

Usually one of two things happen:

1. You start a long-lived branch that is awful to maintain and often impossible to merge. It ends up being a time sink ⏱

2. You and your team make an agreement to make the improvement over time. It gets forgotten about and nothing gets better (in fact usually it gets worse!) 😕

I've seen this happen time and time and again! Sometimes it's introducing a new style rule to a codebase. Other times it's enabling stricter compilation, or decreasing the number of accessibility failures!

**Betterer** works in two stages. The first time it runs a test, it will take a snapshot of the current state. From that point on, whenever it runs it will compare against that snapshot. It will either throw an error (if the test got worse ❌), or update the snapshot (if the test got better ✅). That's pretty much it!

## How does it work?

To get started, you can run the following from the root of your project:

```bash
npx @betterer/cli init
```

That will give you a brand new `.betterer.ts` config file which looks something like this:

```typescript
// .betterer.ts

export default {
  // Add tests here ☀️
};
```

From here, it's up to you to add some tests!

Let's imagine you're working with a codebase that uses [**Moment.js**](https://momentjs.com/). You'd like to migrate away from it for performance reasons.

```javascript
// src/subtract.js
import * as moment from 'moment';

const now = moment();

console.log(now.subtract(4, 'years'));
```

Let's also imagine that you're using **ESLint** in this codebase. One approach to remove **Moment.js** might be to use [the `no-restricted-imports` **ESLint** rule](https://eslint.org/docs/rules/no-restricted-imports), add the `eslint-disable-next-line` comment all over the place, and cross your fingers that people don't just add more... 🤔

Betterer gives us a better option! We can create a test for that specific rule:

```typescript
// .betterer.ts
import { eslintBetterer } from '@betterer/eslint';

export default {
  'no import from moment': eslintBetterer('./src/**/*.js', [
    'no-restricted-imports',
    [
      'error',
      {
        name: 'moment',
        message: 'Please use "date-fns" instead.'
      }
    ]
  ])
};
```

The first time we run the test with **Betterer**, it will look something like this:

![Terminal output showing Betterer running and indicating that the "no import from moment" test has run for the first time](https://raw.githubusercontent.com/phenomnomnominal/betterer/master/docs/images/first-run.gif 'First run')

**Betterer** has now created a snapshot of the current state, stored by default in a `.betterer.results` file:

```javascript
// BETTERER RESULTS V1.
exports[`no import from moment`] = {
  timestamp: 1589459511808,
  value: `{
    "src/subtract.js:566118541": [
      [0, 0, 33, "\'moment\' import is restricted from being used. Please use \\"date-fns\\" instead.", "4035178381"]
    ]
  }`
};
```

The snapshot contains information about the current issues in the code.

The next time we run the test, it will look like this:

![Terminal output showing Betterer running and indicating that the "no import from moment" test has run and stayed the same](https://raw.githubusercontent.com/phenomnomnominal/betterer/master/docs/images/same-run.gif 'Same run')

Now, someone else on the team comes along and doesn't know about the new rule, and they add a new file that uses **Moment.js**:

```javascript
// src/add.js
import * as moment from 'moment';

const now = moment();

console.log(now.add(4, 'years'));
```

When **Betterer** runs on their code, they get a nice big error:

![Terminal output showing Betterer running and indicating that the "no import from moment" test has run and got worse](https://raw.githubusercontent.com/phenomnomnominal/betterer/master/docs/images/worse-run.gif 'Worse run')

Even though a new issue has been introduced, the `.betterer.results` file doesn't change!

Our teammate reads the helpful error message from **ESLint** and they update their code to use **date-fns**...

```javascript
// src/add.js
import { addYears } from 'date-fns';

console.log(addYears(Date.now(), 4));
```

... and once again **Betterer** tells them that the result is the same:

![Terminal output showing Betterer running and indicating that the "no import from moment" test has run and stayed the same](https://raw.githubusercontent.com/phenomnomnominal/betterer/master/docs/images/same-run.gif 'Same run')

Our teammate has a bit of time on their hands, so they decide to fix up our usage of **Moment.js** as well! 🎉

This time when they run **Betterer**, everything is good:

![Terminal output showing Betterer running and indicating that the "no import from moment" test has run and got better](https://raw.githubusercontent.com/phenomnomnominal/betterer/master/docs/images/better-run.gif 'Better run')

There are now no remaining issues, so this test has met its goal. Since the existing issue has been resolved, it is removed from the snapshot in the `.betterer.results` file. This means we can move the rule from **Betterer** over to the normal **ESLint** configuration, so we don't reintroduce the issues again.

Pretty neat eh! That's an example of the built-in `@betterer/eslint` test, but there are [other](https://github.com/phenomnomnominal/betterer/tree/master/packages/typescript) [built-in](https://github.com/phenomnomnominal/betterer/tree/master/packages/regexp) [tests](https://github.com/phenomnomnominal/betterer/tree/master/packages/tsquery) too. And you can of course write your own tests! Check out the [documentation for more details](https://github.com/phenomnomnominal/betterer#custom-tests) (still a WIP 🚧)!

## What's in v1.0.0?

Everything I've mentioned so far has been working for a while! Over the last few months I've really solidified the implementation (basically a whole rewrite to be honest!):

- Better error handling and error messages
- Better issue comparison. It now understands file renames and issues that around within the same file
- The ability to run tests on a single file via the JS API, with `betterer.single`
- A whole bunch more tests!

But I've also added a few key features that are worthy of a 1.0.0 release! 🔥🔥🔥

### Force Update (!)

First things first, you can now run **Betterer** with the `--update` flag, and the snapshot will be updated even if it got worse! This is handy for when you need to ship something, even if it makes it temporarily worse:

```bash
betterer --update
```

And because this is shamelessly stolen from Jest, you can also use `-u`.

### Watch mode (!!!)

This one is huge! All the changes that I made were building up to this. You can now run **Betterer** in watch mode and get feedback as you fix up issues:

![Terminal output showing Betterer running in watch mode indicating that the "no import from moment" test has run on a specific file and got better](https://dev-to-uploads.s3.amazonaws.com/i/z47lyc7qmtnnxov4ggra.gif 'Watch mode')

Same rules apply here, the snapshot will update whenever the test gets better!

There's a bunch of other cool things that could happen with watch mode (gamification much?), so I'm pumped that it's working! 🤩

### VS Code extension (!!!!!)

Way to bury the lede! This is probably the coolest bit! Let's just say it was a build up.

**Betterer** runs entirely in its own world, so the usual **ESLint** or **TypeScript** extensions can't report the issues. But everyone loves seeing red squiggly lines in the code editor, so **Betterer** now has [its very own VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Betterer.betterer-vscode) 🤯!

#### Initialise in a new project

You can run the `betterer.init` command in a project! It will generate config files and update your `package.json` with scripts and dependencies:

![VS Code screen capture output showing Betterer being initialised in a project](https://dev-to-uploads.s3.amazonaws.com/i/7ij09ag7q2v12pv56h5x.gif 'VS Code Extension Betterer init')

#### See all issues in a file

And when you've got some tests setup, it will show you all the existing issues in a file, and when they were first created. And it will show you any new issues as you make them:

![VS Code screen capture output showing Betterer highlighting issues in a project](https://dev-to-uploads.s3.amazonaws.com/i/uxmtcar93enfqgx12orr.gif 'VS Code Extension Betterer issues')

I reckon that's pretty sweet!

### So, what's next?

Well, you're going to try out [**Betterer**](https://github.com/phenomnomnominal/betterer) and open lots of issues and help me make it better(er)! If you'd like to contribute...

- There are _definitely_ bugs to fix
- The test coverage is pretty good, but there aren't any E2E tests for the VS Code extension yet
- The logging and reporting implementation could use some work
- There is so much documentation to be written

What a time to be alive! 🤓

In case you can't tell, I'm unreasonably excited about this and I really think this will help with large and legacy codebases. Please let me know what you think in the comments or hit me up on the [Twitters](https://twitter.com/phenomnominal)!