Search code examples
node.jsmocha.jssinonchaisinon-chai

Mohca testing failure


I need to test storeDocument function when debug = false which will call createStorageDocument. But for my following test code is giving an error 'TypeError: Attempted to wrap undefined property createStorageDocument as function'

What am I doing wrong ?

And I would prefer a solution to stub fetch inside createStorageDocument function rather than createStorageDocument itself, Can someone explain how to do ? I am very new to mocha and node.js

import fetch from 'node-fetch';
import documentStorageResponseMock from '../../../test/mock/documentStorageResponseMock';

const storageGatewayUrl = 'url';
const storageGatewayApiKey = 'key';

/**
 * Merge storage id into booking response.
 *
 * @param booking
 * @param documentId
 * @returns {*}
 */
function mergeDocumentId(booking, documentId) {
  const mergedBooking = booking;

  mergedBooking.successfulBooking.documentId = documentId.id;

  return mergedBooking;
}

/**
 * @param labelledBooking
 */
function createStorageDocument(labelledBooking) {
  fetch(storageGatewayUrl,
    {
      method: 'POST',
      body: {
        document: labelledBooking,
      },
      headers: {
        ContentType: 'application/json',
        'x-api-key': storageGatewayApiKey,
      },
    })
    .then((response) => {
      if (response.ok === false) {
        throw new Error('Failed to create the document!');
      } else {
        return response.json();
      }
    }).catch((err) => {
      throw err;
    });
}

/**
 * Save booking response and add the document id to the response.
 *
 * @param labelledBooking
 * @param debug
 * @param callback
 */
export default function storeDocument(labelledBooking, debug = false, callback) {
  if (debug) {
    callback(
      null,
      mergeDocumentId(labelledBooking, documentStorageResponseMock())
    );
    return;
  }

  callback(null, mergeDocumentId(labelledBooking, createStorageDocument(labelledBooking)));
}

import storeDocument from '../../../lib/documents/storeDocument';

const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');

chai.use(require('dirty-chai'));
chai.use(require('chai-fuzzy'));

describe('merge document storage id', function () {
 
  before(function(callback) {
    sinon.stub(storeDocument, 'createStorageDocument', function (params, callback) {
      return ({id: '1087449a-1248-4430-9bcb-5a61b2766020'})
    });
  });

  it('it creates and appends document id to booking when storage gateway is provided ', function(done) {
    storeDocument({
        integrationId: 'testing',
        successfulBooking: {
          integrationServiceId: 'service-id',
          bookingReference: '#ref',
          shippingTaxInclusiveCharge: { amount: 10, currency: 'EUR' },
          pricedAt: '2016-05-20T15:00:00Z',
          documentation: {
            labelDocument: 'ero32ukj32hr3h'
          }
        }
      },
      false,
      (error, booking) => {
        expect(booking.successfulBooking.bookingReference === '#ref').to.be.true;
        expect(booking.successfulBooking.documentation !== undefined).to.be.true;
        expect(booking.successfulBooking.documentId !== '').to.be.true;
        done();
      });
  });
});


Solution

  • Part of the problem is that createStorageDocument returns a Promise, not a scalar value. First I'd redesign storeDocument.

    /**
     * Save booking response and add the document id to the response.
     *
     * @param            labelledBooking
     * @param {Function} createStorageDocument
     * @param {Function} callback
     */
    export default function storeDocument(labelledBooking, 
        createStorageDocument,
        callback) {
      createStorageDocument(labelledBooking)
        .then(function (documentId) {
            callback(null, mergeDocumentId(labelledBooking, documentId));
        })
        .catch(callback);
    }
    

    Ok so what's happen here is we are using dependency injection to inject the object that will store the object, and we are handling the Promise correctly.

    Then you want to fix createStorageDocument:

    export function createStorageDocument(labelledBooking) {
      return fetch(storageGatewayUrl, {
          method: 'POST',
          body: {
            document: labelledBooking,
          },
          headers: {
            ContentType: 'application/json',
            'x-api-key': storageGatewayApiKey,
          },
        })
        .then((response) => {
          if (response.ok === false) {
            throw new Error('Failed to create the document!');
          }
    
          return response.json();
        });
    }
    

    Here I've inserted return before fetch, and you don't need the catch anymore (it wasn't going to work anyway). I've exported it so you will have to use that in your actual implementation.

    Ok, now for the test. You don't need Chai - it just complicated things. Use Sinon sparingly and only when you know Node better. Your test can become something like this:

      it('it creates and appends document id to booking when storage gateway is provided ', function(done) {
          let createStorageDocumentStub = function (labelledBooking) {
              return Promise.resolve('documentId???')
          }
        storeDocument({
            integrationId: 'testing',
            successfulBooking: {
              integrationServiceId: 'service-id',
              bookingReference: '#ref',
              shippingTaxInclusiveCharge: { amount: 10, currency: 'EUR' },
              pricedAt: '2016-05-20T15:00:00Z',
              documentation: {
                labelDocument: 'ero32ukj32hr3h'
              }
            }
          },
          createStorageDocumentStub,
          (error, booking) => {
            if (error) {
              return done(error);
            }
            assert(booking.successfulBooking.bookingReference === '#ref'));
            assert(booking.successfulBooking.documentation !== void 0);
            assert(booking.successfulBooking.documentId !== '');
            done();
          });
      });
    

    What I've done is create a stub (not a mock, that's a different thing) for the function that is supposed to store the document, and I've replaced your assertions with just plain old assert that is included with Node. And don't forget to handle errors in your tests (they can still bite you).

    To be honest though, it would be better if storeDocument also returned a Promise rather than having to use an ugly callback function.

    I realise that's probably a lot to take in.