Testing and Jest

Jest is one possible test framework to use for Javascript. I got into using because of React, but found it suited my needs generally so I stuck with it.

Install

npm install --save-dev jest  

Get the babel configuration set up

Jest runs on node, so whatever code you are running through it needs to get transpiled for node. Any non standard code (react, emotion) also needs to get transpiled.

The key part is targets: set to a node version

The “targets”: { “node”: 8 } under preset-env is the key part. The React/Emotion configuration is specific to my component projects.

  "jest" : {
     "presets": [ 
       "@babel/preset-react", 
     [ "@babel/preset-env", 
     {  
       "targets": {
         "node": 8
       }
     }],
     ],
     "plugins": ["emotion"]

Configure package.json -

Need jest listed as the test script at the very least. The second one runs jest in daemon mode so you can quickly re-run the tests or have it run on code changes.

  "scripts": {  
    "test": "BABEL_ENV=jest jest",
    "test-watch": "BABEL_ENV=jest jest --watch",
  },

Writing test files -

If the original file is filehandler.js, the test file is filehandler.test.js. Jest searches through your project and will do it’s best to find tests and run them by several naming conventions, but that is the one I use.

Run the tests -

A couple different variants to try out. Verbose reports in individual test when they are otherwise hidden underneath the module they are testing.

npm run test  
npm test -- --verbose  
npm run test-watch 

Expect() is the key function for testing

Although the primary purpose of this article will be showing how to do various different mock setups, expect() is the main jest function to set up tests. Expect takes the a call to the unit of code being run as the input and then runs matchers on the output to determine if the test passed or failed. For better information on this, check out the docs or other write ups focusing on that aspect.

A few expect call examples

Expect takes results from running a function and runs the matcher on it to see if the result is what was expected.

  let cursor = await dbconn.find('test')({a: 1},{'projection':{b: 1}, limit: 1})
  let rsltarr = await cursor.toArray()
  expect(rsltarr).toEqual([{_id:2, b: 4}])

Mocks in jest

Mocks are function, module, or any other code unit substitutes so that code is tested in isolation. The substitute gives specific known responses.

What to Mock, and why

Testing isolates units of function, so mocking outside modules usually makes sense because those modules should be independently tested, and any problems in that code should not cause failures in tests of other code.

Some code, especially IO, should always be mocked

With IO like file system, database, network, you almost always want to mock to avoid dealing with failures that can occur with these sorts of requests due to random failures or system/network failures that have nothing to do with your code.

Handle those failures anyway

You should still simulate failures of IO like null or error returns, timeouts, and make sure your code deals with those in a reasonable manner without having them crop up randomly during tests.

Mock functions

By default, they mocked functions just record everything that is happening with the function, but do nothing. You can also have them give specific outputs, or a function to execute when they are called.

They record calls, inputs, other info

Mock functions allow tracking what has been done with them so you can determine if they have been called the correct number of times, with the right arguments, and so on.

var mock1 = jest.fn()  
mock1()  
mock1.mock.calls.[length, number …]
// The mock function then has a host of properties to see how it was used.

Providing return values from a mock

It is possible to provide single ouputs, an output to always return, or a combination of both.

var mock2= jest.fn()  
// returned the value once  
mock2.mockReturnValueOnce('Example/path/file.ext')  
// keep returning anytime the mock is called.  
mock2.mockReturnValue('simple/path')  

In the above case, the mock function will return the first value once, and then the other value for any further calls.

The __mocks__ folder sets up mocks

This needs to be located where the modules you want to mock are. For modules in node_modules folder, use projectroot/mock/filename.js

Working with scoped package mocks -

Just do the scope and then package. This is an example path for a scoped module:

./maindir/__mocks__/@jadesrochers/subprocess.js  

When to use mocks folder or not

Use a __mocks__ folder when the mocked fn or module is used by the code you are testing somewhere, but not directly by your test calls.
The __mocks__ setup assure the mock is used everywhere it might be called by the module being tested. If you need to mock something you use directly, then creating a jest.fn() there should work fine.

Mocking a function vs the whole module

Mock a module when you don’t want any stray calls being used that will cause unwanted IO. Mock functions can be used when you don’t want to touch other parts or know exactly what will and should be used and don’t need to mock other portions.

Mock an entire module

This is in the contents of a file in mocks. This will automatically replace all methods of a module with mocks.

const subp = jest.genMockFromModule('../subprocess')  

Give individual functions specific mock functionality

Give them more specific functionality. The whole module is still mocked in this case, but the default is for the mock functions not to do anything. This allows them to do something specific.

var mockShell = jest.fn()  
var mockOutput = jest.fn(n => n.stdout)  
mockShell.mockReturnValueOnce({stdout: '23200 filename.example', stderr:   
''})
mockShell.mockReturnValue({stdout: '', stderr: ''})  
subp.shellCommand = mockShell  
subp.commandOutput = mockOutput  
module.exports = subp  

Initiate the mock in the test file

Jest will not activate a mock unless you tell it to.

jest.mock('./subprocess')  

Mock single functions

In node you need to set up the mocks folder, and create a module that pulls in the original module, mocks the desired functions, and then exports the module with the mock functions inserted.

Pull the original module in with the jest requireActual function

This avoid an infinite loop problem.

const fh = jest.requireActual('../filehelpers') 

Then replace just the functions you want to mock

And export the modified module that has the mocks. This is an example with my filehelpers library when I don’t want to be actually reading from the file system.

var mockSize = jest.fn()  
var mockLines = jest.fn()  
mockSize.mockReturnValue(70)  
mockLines.mockReturnValue(124600)  
fh.getFileSizeMb = mockSize  
fh.getFileLines = mockLines  
module.exports = fh  

How to export the mocks

// Export syntax works fine
exports.mongoMaker = mockMaker
// But you can also construct an object and export that
fh.getFileSizeMb = mockSize
fh.getFileLines = mockLines
module.exports = fh

Watching for errors throw in synchronous code

Use the .toThrow() function and wrap the function to be tested in a blank function so that it can be called, which is required for this to work. This is the setup for a non-async function. I will show how to deal with the async case next.

expect(() => subp.commandOutput({stdout: 'something', stderr: 'else'})).toThrow(/error output/)  

Dealing with Errors from Promises

Using plain .toThrow() won’t work, you need to deal with the promise first by using the .rejects() function that looks for a promise rejection.

// This will watch for a rejection, and the error that comes from that.  
Await expect(rslt).rejects.toThrow(/pattern/)

Dealing with async stack errors

This function has several levels of async that could throw a promise rejection, so in this case also use the reject option first, then use the toThrow().

await expect(dbconn.findFromDb('test',{a: 1})).rejects.toThrow(/topology was   destroyed/)  

Mocking MongoDB

You could set up a temporary Db, create a fake one, or use a mongo memory db. Here is an example of the Memory Server. I connect to it with my mongotools library and then use it normally.

var { MongoMemoryServer } = require('mongodb-memory-server')  
async function setupMemDb(){  
  mongoServer = new MongoMemoryServer();  
  settings.urldb = await mongoServer.getConnectionString();  
  settings.database = await mongoServer.getDbName();  
  dbconn = await mongotools.mongoMaker(settings);  
}

Then you can use the memory db like a normal one.

dbconn.insertIntoDb('test')([{_id: 1, a: 1, b: 2, c: 5},{_id: 2, a: 1, b: 4, c: 11}])  
var data = await dbconn.findFromDb('test')({a: 1})   

Alternative; a real mongodb

My recommandation for using this path is to setup a mongodb instance with docker, but use an unnamed volume so that the db is created each time. It would be best to start up/shut down the mongodb instance directly from the test code each time so you know it is always a fresh instance.

Mock buffer

Use Buffer.from() with JS objects formatted as a string to put the object in a buffer that could then be decoded. Can also just do strings, numbers, array, etc.

let data = Buffer.from('{"Date": "1998-05-30", "RegionName": "NewRochelle",   
"AveragePrice": "126345", "upperquarter": 234891, "avgpersquareft": "", "pricetrend":   
""}')
 var data = Buffer.from('[1,2,3,4,5]')  
 var data = Buffer.from('100 230 350')  

Get character codes from buffer -
Buffer.from(‘abcd abcd’).toJSONao
Make and empty buffer, write to it -
var buf1 = Buffer.alloc(20) - A buffer with 20 bytes
buf1.write(‘test buffer’)

Mocking curried functions

The mocking has to be set up to allow multiple calls. Simplest way is to put a mock inside a mock.

var nested = jest.fn()  
var mockCreateIndex = jest.fn(n => nested)  

Mock composed returns

Take something like this with objects put together:

  return Object.assign(  
    {},
    {add: dataAdd},  
    {flush: flusher},  
  )

It can be mocked by creating a mock that returns an object with appropriate named mocks.

assigned.add = jest.fn()  
assigned.flush = jest.fn()  
mockStoreWrite = jest.fn(a => b => assigned)  

Mock streams

Setting up a readable stream allows you to generate data for other streams, so that is a good place to start.

var testStream = new Readable({  
  objectMode: true,  
  read(){}  
});

Piping to process.stdout is just one way to see what your stream is producing.

testStream.pipe(process.stdout);  

Use push to give it data. If you want something more complex, set up an array or object and push the value inside that.

testStream.push('Something')  
testStream.push(Buffer.from('1,2,3,4,5'))