Making test stub setup effortless using Sinon
Tags: node, testing
Published: 2023-02-03 02:10:38

---
title: Making test stub setup effortless using Sinon
published: true
date: 2023-02-03 02:10:38 UTC
tags: node,testing
---

<em><strong>Originally published at <a href="https://www.coreycleary.me/making-test-stub-fake-setup-effortless-using-sinon">coreycleary.me</a></strong>. This is a cross-post from my content blog. I publish new content every week or two, and you can <a href="https://www.coreycleary.me/about/">sign up to my newsletter</a> if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets and other freebies.</em>

When you're writing tests, it's often the general setup like creating fake data and setting up stubs/mocks/fakes that can hold you back. Sometimes to the point where you end up skipping writing the tests altogether...

This setup can feel tedious and creates more work to do on top of having to write the rest of the test, come up with the assertion, and make sure you're covering reasonable test scenarios.

But by using the testing library Sinon - and having a few real-world/practical examples - test setup can become effortless.

I'm going to go over one of those examples in this post to help explain how to write stubs/fakes so you can quickly write your tests and get back to writing application code.

## The unit under test
The unit under test here - the `getBinarySliceFromFile()` function - returns a slice of a binary file based on positional arguments.

```javascript
export const getBinarySliceFromFile = (start, offset, fileName) => {
  const fileBinary = fs.readFileSync(`./binaries/${fileName}`)
  return fileBinary.subarray(start, start + offset)
}
```

- `readFileSync()` returns the file in binary/Buffer format.
- `start` here is the starting point to slice the file, so you could slice from any arbitrary point
- `offset` is sort of like the end point to slice, but rather than an explicit end, it's used along with `start` to define the end

The issue is that in this function it's reading from the file system via the Node `fs` module. We want to test the output of this function, so we need to be able to control what `fileBinary` is. And we can't hardcode the directory path + filename.

To do this, we can stub out / fake the `fs` module. This will allow us to set specifically what to return from the `fs.readFileSync()` function, which is what we want.

## The test code

```javascript
describe('getBinarySliceFromFile()', () => {
  afterEach(() => {
    sinon.restore()
  })

  it('should return a slice of the binary starting from an index point', () => {
    const index = 1
    const offset = 1

    // fake a binary
    const fakedFileBinary = Buffer.alloc(2)
    // output of the below is <Buffer 81 0b>
    fakedFileBinary.writeUInt16LE(2945)
    // create the Sinon fake, so that it returns the fake binary above
    sinon.replace(fs, 'readFileSync', sinon.fake.returns(fakedFileBinary))

    const slice = getBinarySliceFromFile(index, offset)

    expect(slice.length).to.equal(1)
    // notice that the below is the last block in <Buffer 81 0b>, which proves the "slicing" works
    expect(slice).to.deep.equal(Buffer.from([0x0b]))
  })
})
```

The fake setup in the code above happens here:
```javascript
    // fake a binary
    const fakedFileBinary = Buffer.alloc(2)
    // output of the below is <Buffer 81 0b>
    fakedFileBinary.writeUInt16LE(2945)
    // create the Sinon fake, so that it returns the fake binary above
    const readFileSyncFake = sinon.fake.returns(fakedFileBinary)
    sinon.replace(fs, 'readFileSync', readFileSyncFake)
```

On the first two lines, it's nothing Sinon-specific but just assigning a Buffer (which is binary data) to a variable and then writing data to that Buffer.

The next part is where Sinon comes into play:
```javascript
    const readFileSyncFake = sinon.fake.returns(fakedFileBinary)
    sinon.replace(fs, 'readFileSync', readFileSyncFake)
```

This is using the Sinon [fakes](https://sinonjs.org/releases/latest/fakes/) API, which are similar to stubs and likely what you are more familiar with hearing. According to the Sinon docs, "Fakes are alternatives to the Stubs and Spies, and they can fully replace all such use cases."

`sinon.fake.returns` lets us setup the fake using the simulation of the file binary. And the `.replace()` function lets us specify the `fs` method to "overwrite" - in this case `readFileSync` - and instead return the fake.

That code is pretty simple to setup but very powerful. Now we don't have to modify the function definition for `getBinarySliceFromFile()` and can instead call it as usual. When it gets called, when it comes time to hit the `fs` code Sinon will have swapped that out with the fake. And you can get an output from `getBinarySliceFromFile()` to then make assertions on. A pretty effortless way of doing it.

### Test cleanup

You may have noticed there is an `afterEach()` block defined at the beginning of the test suite:
```javascript
  afterEach(() => {
    sinon.restore()
  })
```

Doing this will reset Sinon and the defined fakes after each test, which is a good practice for test "cleanliness"  so you're starting from a clean slate with each test and there aren't leftover fakes from previous tests that never got reset.

Note that fakes are available in Sinon with version 5 and up. If you are using a version below that I would recommend upgrading, but if you can't do that you can use the [stub API](https://sinonjs.org/releases/latest/stubs/) that Sinon provides, and is very similar.

## This vs. passing in as an argument

One thing we could have done and avoided Sinon and fakes entirely would have been to pass the file binary in as an argument to the function, like so `getBinarySliceFromFile(start, offset, fileBinary)` (note the last arg was changed from the file name to the file binary). If we did that it would let us pass in a simulated binary (just like how we set it up using a Buffer above) in the tests rather than using Sinon.

This can be a valid way, but I don't think it's great to do that here. Any code that calls `getBinarySliceFromFile()` will now have to call `fs.readFileSync()` and pass the result of that to `getBinarySliceFromFile()`, which starts to break encapsulation. Whereas if we keep `fs.readFileSync()` in the function body, consumers of `getBinarySliceFromFile()` only need to pass in a file name, and that function is responsible for fetching the file binary. This is better encapsulation in my opinion.

## Summary

The next time writing tests is holding you back because either you don't know how to setup fakes/stubs, or the setup is just too tedious, use Sinon and the example test code above to help guide you and make it very easy to get those tests written!

And if you're feeling like you don't know where to even begin with real testing examples and patterns, the below posts will help you with that:

- [Using spies as a way to test side-effects in Node](https://www.coreycleary.me/using-spies-as-a-way-to-test-side-effects-in-node)
- [Know what to test using these recipes: Node service that calls a database](https://www.coreycleary.me/know-what-to-test-using-these-recipes-node-service-that-calls-a-database)
- [Real world testing recipes: Node service that calls an external API](https://www.coreycleary.me/real-world-testing-recipes-node-service-that-calls-an-external-api)
- [Real world testing: Using business and technical requirements to know what to test](https://www.coreycleary.me/real-world-testing-using-business-and-technical-requirements-to-know-what-to-test)

Love JavaScript but still getting tripped up by unit testing, architecture, etc? I publish articles on JavaScript and Node every 1-2 weeks, so if you want to receive all new articles directly to your inbox, <a href="https://www.coreycleary.me/about/">here's that link again</a> to subscribe to my newsletter!