Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking a module function which is exported and called within the same module?

new to unit testing and the concepts of spies, stubs and mocks.

I want to test the verify method from password.js from code below, but I'm unable to stub the hash function within the test file.

As verify uses the hash function and the hash function is exported, I though I should stub the hash function to return a fixed response rather the actually calling hash. As I'm not trying to test the hashfunction.

Problem: The created stub for hash function is not called when testing verify.

Side question 1: Should I be focusing on testing the logic of the function itself rather the other called functions?

Main Question: (answered) How do stub a module function that is called within the same module?

Side question 2: How would I go about stubbing hash if it where not exported but stayed only within the module?

Code

password.js

/**
 * Creates a hash based on a salt from a given password
 * if there is no salt a new salt will be generated
 *
 * @param {String} password
 * @param {String} [salt] Optional value, if not given will generate a salt
 */
function hash (password, salt) {
  // returns a promise that resolves an hash object with hash and salt key
  // example: {hash: '2512521nska...', salt: '25hbBhhsfa...'}
}

/**
 * Verifies if a password matches a hash by hashing the password
 * with a given salt
 *
 * @param {String} password
 * @param {String} hashString
 * @param {String} salt
 */
function verify (password, hashString, salt) {
  return hash(password, salt)
    .then((res) => res.hash === hashString);
}

module.exports = {hash, verify};

password.test.js

import test from 'ava';
import sinon from 'sinon';

import passwordModule from './password';

test('verify - should verify password', function * (t) {
  const password = 'test-password';
  const salt = null;
  const hash = 'my-hash';

  const hashStub = sinon.stub(passwordModule, 'hash', (password, salt) => Promise.resolve({hash, salt}));

  const verified = yield passwordModule.verify(password, hash, salt);

  t.true(verified);

  hashStub.restore();
});

Setup

  • Node v6.2.0
  • Ava v0.15.2
  • Sinon" v1.17.4

Tests and the modules are transpilled with babel. But the module isn't using ES6 module exports as it is used in node env without transpilling.
I'm transpilling all code during testing so that it is future proof and the save env can be used for both frontend and backend code, where the frontend code is transpilled.

like image 291
Renārs Vilnis Avatar asked Jun 07 '16 18:06

Renārs Vilnis


2 Answers

Side question 1: Should I be focusing on testing the logic of the function itself rather the other called functions?

Part of testing verify is making sure that it calls hash properly. Also in a more general sense, and not so much applicable to your code, is that a function should handle errors thrown by other functions properly. In your case, you're propagating any errors to the callers of verify, that's why it doesn't really apply.

Main Question: How do stub a module function that is called within the same module?

You found an answer already, but see below for an alternative.

Side question 2: How would I go about stubbing hash if it where not exported but stayed only within the module?

A great module for this is rewire, which allows you to overwrite private (non-exported) variables inside a module. It will also help with your "main question", because it allows you to leave the code as before.

Here is your test on rewire:

import test   from 'ava';
import sinon  from 'sinon';
import rewire from 'rewire';

const passwordModule = rewire('./password');

test('verify - should verify password', function * (t) {
  const password = 'test-password';
  const salt = null;
  const hash = 'my-hash';

  let hashStub = sinon.stub().returns(Promise.resolve({hash, salt}));

  // Replace the `hash` function inside your module with the stub.
  let revert   = passwordModule.__set__('hash', hashStub);

  const verified = yield passwordModule.verify(password, hash, salt);

  t.true(verified);

  // Revert to the original `hash` function.
  revert();
});
like image 200
robertklep Avatar answered Sep 18 '22 14:09

robertklep


Found out the answer for the main question within stackoverflow:
Stub module function called from the same module

To fix the issue I needed to call the hash the exported hash function rather then the private.

exports.hash = function hash (password, salt) {
  // returns a promise that resolves an hash object with hash and salt key
  // example: {hash: '2512521nska...', salt: '25hbBhhsfa...'}
}

exports.verify = function verify (password, hashString, salt) {
  return exports.hash(password, salt)
    .then((res) => res.hash === hashString);
}

Still would like to know the answers for the side questions.

like image 34
Renārs Vilnis Avatar answered Sep 21 '22 14:09

Renārs Vilnis