Unit Testing with Chai, Proxyquire, and Sinon

Farooq Haider
Ordergroove Engineering
4 min readAug 5, 2020

Unit testing is defined as testing an individual unit of work in isolation, without testing any dependency.

It’s a best practice to have reasonable code coverage using unit tests. Code coverage measures how much of a section of code has been executed by tests. The best way to get the maximum coverage is to cover as many branches of code as possible, but that’s a topic for another blog post.

Today we would like to focus on performing unit-tests using chai + proxyrequire and sinon. I will explain these libraries one by one shortly but before that it’s important to know that these libraries are available as node js packages and they are primarily used for node js testing.

Wondering what is chai?

It’s a behavior driven/test driven assertion library. We will mainly be using chai to support asserts; assertion is the way to build up expectations during the test and it makes sure that the test fails if it does not meet the required behavior.

Here is the link to the list of all the assertions supported by chai e.g isOkay, deepEqual, isTrue, exists, notExists, isDefined, isFunction, etc.

And what’s proxyquire?

In the unit-testing paradigm, we don’t want to test the code from imported modules but instead we want to setup the outcome of those dependencies with return values. This allows us to focus on the problem at hand, testing a given module’s functionality.

Proxyquire is used to proxy a required module in node js. Having said that, if we require any dependency into the given file, we can proxy that with a stub, and only test code for given file.

Few words about sinon

Sinon is used to create stubs, mocks, and spies in javascript. A Stub is used to avoid a specific method from being called directly. We can use stubs to return a specific value from a method or have it throw an exception. Stubs can also control what to return when it’s called with particular function parameters, or based on the order of it’s invocation.

Ordergroove + SalesForce Commerce Cloud

Here at Ordergroove, we are working together with different platforms to power subscription and relationship commerce. One of those platforms is SalesForce Commerce Cloud(SFCC). In order to integrate Ordergroove with SFCC, we built a cartridge that can be installed on an SFCC powered ecommerce store and acts as a bridge between SFCC and Ordergroove.

Code Example

Enough of the definitions, let’s dive into an example using these libraries. First we should include the following packages inside our package.json: chai, proxyquire, sinon

Let’s say we have to test the function below. This example is inspired from applying promotion and discounts to a basket using cartridge helper classes. We don’t have access to the implementation of those helper classes in the SFCC cartridge ecosystem and proxyquire is the great tool in this situation to stub the required modules.

var PromotionMgr = require(‘dw/campaign/PromotionMgr’);var calculateHelpers = require(‘*/cartridge/scripts/hooks/cart/calculateHelpers’);function applyPromotion(basket) {
calculateHelpers.removeAllPriceAdjustments(basket);
var ioiPromotion = PromotionMgr.getPromotion(‘OrdergrooveIOI’);
if (!ioiPromotion) {
return;
}
var discounts = PromotionMgr.getDiscounts(basket);
PromotionMgr.applyDiscounts(discounts);
}

This function is requiring two different modules “PromotionMgr” and “calculateHelpers” as part of the “require” call and then using those objects to perform certain operations.

The question is how can we test the “applyPromotion” method without actually calling the dependencies and ensuring that they have been properly invoked with the correct parameters.

Writing a Test

From the following lines, we will start defining the elements of our tests. First, we include all three libraries

var assert = require(‘chai’).assert;
var proxyquire =require(‘proxyquire’).noCallThru();
var sinon = require(‘sinon’);

Since we only want to make sure that the method is handled correctly, we can initialize the variables with random data; we don’t need the actual structure of the data.

var basket = ‘testBasket’;
var ioiPromotion = ‘testIOIPromotion’;
var discountsWithPromotion = ‘testDiscountsWithPromotion’;

Next is to initialize the stubs using Sinon stubs. We will also be using the “returns” method of each stub to define what value will be returned when that method is invoked.

var getDiscountsStub = sinon.stub();
getDiscountsStub.returns(discountsWithPromotion);
var getPromotionStub = sinon.stub();
getPromotionStub.returns(ioiPromotion);
var applyDiscountsStub = sinon.stub();
var removeAllPriceAdjustmentsStub = sinon.stub();

Here comes the magic, we will be using proxyquire to require the ‘calculateDiscountsmodule and stub the dependencies. Here PromotionMgr methods like getPromotion, getDiscount, and applyDiscount are mapped to stubs.

var calculateDiscounts = proxyquire(‘../../../../../../cartridges/int_ordergroove/cartridge/scripts/hooks/cart/calculateDiscounts’, {
‘*/cartridge/scripts/hooks/cart/calculateHelpers’: {
removeAllPriceAdjustments: removeAllPriceAdjustmentsStub
},
‘dw/campaign/PromotionMgr’: {
getPromotion: getPromotionStub,
getDiscounts: getDiscountsStub,
applyDiscounts: applyDiscountsStub
}
});

Finally, we will be adding actual tests to test the function behavior when ioiPromotion is present.

describe(‘calculateDiscounts’, function () {  beforeEach(() => {
removeAllPriceAdjustmentsStub.reset();
getPromotionStub.reset();
getDiscountsStub.reset();
applyDiscountsStub.reset();
});
it(‘should apply discounts correctly’, function () {
calculateDiscounts.applyPromotion(basket);
assert.isTrue(getPromotionStub.calledWith('OrdergrooveIOI'));
assert.isTrue(getDiscountsStub.calledWith(basket));
assert.isTrue(removeAllPriceAdjustmentsStub.calledWith(basket));
});
});

The “reset” method is called on the stub before each test. The first line of code “calculateIOI.applyPromotion” is used to actually call the function under test and then is followed by the assert commands we are using to verify the expected behavior. These assert commands are powered by the chai assert library.

Adding another test for the case if ioiPromotion is not present.

  it(‘should shouldnot apply discounts’, function () {
var promotions = [];

getPromotionStub.returns(promotions);
calculateDiscounts.applyPromotion(basket);
assert.isFalse(getDiscountsStub.calledWith(basket));
assert.isTrue(removeAllPriceAdjustmentsStub.calledWith(basket));
});

Conclusion

Putting it all together, it’s a great combination to use proxyquire together with chai and sinon, where proxyquire handles proxying external dependencies, chai supports assertions and sinon helps build mock objects. Also there is active support from the community.

For further details on these libraries, please follow the links for proxyquire, sinon, and chai.

--

--