Jasmine spy on exported function on NodeJS - node.js

I've had trouble spying on exported function in a NodeJS (v9.6.1) app using Jasmine.
The app is written in typescript, transpiled with tsc in a dist folder to be executed as javascript.
App
I have a Foo utils file (foo.utils.ts) which exports functions:
import {readFile} from "fs";
export function getData(filePath: string){
const data = readFile(filePath)
// various checks happens here.
return data;
}
And a Bar class in a bar.ts file:
import {getData} from "../utils/foo.utils
export class Bar {
public do(){
// ... does stuff
const data = getData(filePath);
// etc.
}
}
Test
Now I'm trying to spyOn the exported getData method to check if it've been called and to return a mocked value. I don't want to read file in my unit test (and even to use the real getData method at all)
The bar.spec.ts file for testing:
describe("Bar", ()=>{
let bar: Bar;
beforeEach(function() {
bar = new Bar();
})
it(" should do stuff", ()=>{
const spy = spyOn(???, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
})
});
Problems
As it is a NodeJS app, I've been trying to use global as the object to spy on, but I get the error:
Error: : getAttachementData() method does not exist
I've also tried to add
import * as fooutils from ".../src/utils/foo.utils
And spy on fooutils but I still goes through the exported function (and crashes when it tries to read the file...)
Now I'm kinda lost. From what I've found it's not really possible to mock an exported function (even though they should be added to the global object).
I thought about refactoring the utils file to create an utils class which exports static method and spying on them.
Questions
Am I doing something wrong ?
Is it possible to spy (and replace) exported function ?
Would using static method in class (rather than export function) work ?
Any other way to that ?

You can wrap getData in an object { getData } and write your unit test as follows.
import { getData } from "../utils/foo.utils";
...
it(" should do stuff", () => {
const spy = spyOn({ getData }, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
});

Related

How to mock variables in nodejs

When writing nodejs unit test case I want to mock variables.
let abc = awsMock.service.lambda;
In above code, awsMock I am creating and passing to method but service is json object which I want to mock. How I can pass some dummy value when above line is executed?
I want awsMock to be mock object only as I want to mock method using this after above code is executed.
I tried to stub variable but it didn't work
I recommend you encapsulate that clases who you need to mock in a service container and you can replace that values in your mock files.
I will use a minimal example case with three files:
- sc.js : The service container
- main.js: The main function
- test.js: The main function for tests
sc.js
// FAKE CLASS
class Lambda {
foo() {
return "real";
}
}
// END FAKE CLASS
// CODE START HERE
// The service container: could be an object, class, etc...
const serviceContainer = {
lambda: new Lambda(),
};
// Export SC
module.exports.sc = serviceContainer;
// Export properties with a wrap function (this is more easy to handle)
module.exports.getLambda = () => serviceContainer.lambda;
main.js
const { getLambda } = require("./sc");
module.exports.main = () => {
// Get lambda
const lambda = getLambda();
// Print foo()
console.log(lambda.foo());
}
test.js
const { sc } = require("./sc");
const { main } = require("./main");
// Exec before mock
main();
// Mock replace
sc.lambda = new class LambdaMock {
foo() {
return "mock";
}
}
// Exec after mock
main();
If you execute node test.js the output will be:
real
mock
So, you could use the getLambda() function in each file to get the Lambda Instance or the Mocked Lambda Instance.
NOTE: As a recommendation, you must wrap the AWS Lambda class in another class to make ensure the real and the mocked version will have the same functions.

sinonjs - stub a library referenced internally as a function using node modules (no require)

I have an external library that is exported as a function, in the Stub documentation it only accepts an input with the first parameter as object and the second parameter as method , so how could I stub a library that is exported as a function in a Node ES Modules environment (without Commonjs)?
(In my specific case, I had used a library that use the internet to work, and I wanted to test derivated functions without accessing the internet, so I want to stub the external function library)
Attempts:
I couldn't use solutions like proxyquire as it is a solution based on require and module cache deletion, which are not supported within Node's ES modules.
I don't want to use proxyquire-universal because it simulates the operation of require in normal ES, and it's just a function in the whole project that I wanted to test, I was looking for a simpler solution
Changing the import mode doesn't work as it's not recompiled like in babel, so even if I import as import * as obj from 'lib' only the function name is changed
I had this error environment with 3 files:
An external library called "sum" for example, which I don't want to change, exported as follows:
External Library: externalSum.js
module.exports = function sum(a, b){
console.log(">>>>> running without stub <<<<<")
return a + b
}
This library used in the middle of a project file called mathProblems
Internal File: mathProblems.js
import sum from 'externalSum'
export function sumMore1(a) {
return sum(a, 1);
}
And I have a test file
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from 'externalSum'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj) // NOT STUBBING
sum_stub.withArgs(1, 1).returns(2) // NOT STUBBING
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I didn't find this solution anywhere on the internet, i found some solutions that work for node with request or babilon, but not for my case using ES Modules:
https://github.com/sinonjs/sinon/issues/562
https://minaluke.medium.com/how-to-stub-spy-a-default-exported-function-a2dc1b580a6b
So I wanted to register the solution in case anyone needs it.
To solve this, create a new file, which can be allocated anywhere in the project, in this case I will call it sumImport.js:
Internal File: sumImport.js
import sum from 'externalSum';
// export as object
export default {
sum
}
The object needs to be called inside the created function I want to test, and changed the import way:
Internal File: mathProblems.js
import sumObj from './sumImport.js';
export function sumMore1(a) {
const { sum } = sumObj;
return sum(a, 1);
}
And I finally managed to import as an object in the test:
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from '../sumImport.js'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj, "sum") // STUBBED
sum_stub.withArgs(1, 1).returns(2) // STUBBED
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I hope it helps someone and if someone else has some better solutions I would also be grateful!

How to mock document.currentScript to return a valid script object with data attributes

I'm trying to write some tests in jest for an API that is intended to be shared with third parties. The inclusion method used by the third parties is a script tag which initiates the script but also passes along some data attributes via document.currentScript.
Here is a very cut down version of the code with the relevant parts:
// clientLib.js
export const getCurrentScript = () => document.currentScript;
export const initLib = () => {
const currentScript = getCurrentScript();
const reference = currentScript.getAttribute('data-reference');
const lang = currentScript.getAttribute('data-lang');
return new clientLib(reference, lang);
}
export class clientLib {
constructor(reference, lang) {
this._brand = reference;
this._market = lang;
}
}
window.clientLib = initLib();
// html
<script src="clientLib.js" data-reference="12345" data-lang="en-GB"></script>
What I'd like to be able to do in my test is something like this, but I've been unable to get anything to work:
// clientLib.test.js
import {
getCurrentScript,
initLib,
clientLib
} from './clientLib';
// here I want to mock the output of getCurrentScript() since document.currentScript does
// not exist, and I need the mock to return a script object with the two data attributes
// (ideally I need to be able to recreate this mock with both, either or none of the attributes)
// to test other cases
describe('initLib', () => {
it('returns a new instance of the library based on script attributes', () => {
window.clientLib = initLib();
// in here I should then be able to access properties on my lib on the window object
});
});
I did manage to get this working using an external setup file but for logistical reasons I don't think I can use this approach. I've tried to mock it in the test file itself and keep coming back to an "Invalid variable access: document" error. Is there a way to do this that I'm missing?

Testing async methods using Mocha, Chai, node.js

I have a very simple code structure like this
TestWorks.ts
const axios = require('axios');
export class TestWorks{
async getUsersList(param1:TestModel, userDetail:any){
console.log("BEGIN -- ... ");
And then this is my test class
MyTest.ts
const testworks = require("../src/interfaces/TestService/TestWorks");
it('Get Users', async () => {
var x = await testworks.getUsersList({}, {});
expect(x).to.be.an("object");
});
but I am seeing the following error, unable to figure out what the issue could be. The paths are definitely right, not an issue with the file paths of where the files are
Get Users:
TypeError: testworks.getUsersList is not a function
at C:\Users\xxxxxx\Documents\xxxxx\test\test-server.test.ts:53:28
testworks refers to the module (or whatever TypeScript exports) because you use require(). You should use import for TypeScript modules.
import { TestWorks } from '../src/interfaces/TestService/TestWorks';

Jasmine spy on function exported with no parent object and imported using ES6 in NODE

Having done a lot of research I cannot find a way to mock functions that are exported with no parent object. For example I'm trying to mock functions exported the following way:
module.exports = function thing(event) {};
OR in ES6
export function thing(event) {};
When importing these into a test file I try importing like this:
import {thing} from 'emvio-util-responses;
//call some function that calls thing()
spyOn(???, 'thing').and.returnValue({});
expect(???.thing).toHaveBeenCalled();
I have tried many ways of accomplishing this but the mock is not called.
Some suggest importing * and providing an alias as a parent object. like this:
import * as SomeObj from 'emvio-util-responses;
//call some function that calls thing()
spyOn(SomeObj , 'thing').and.returnValue({});
expect(SomeObj .thing).toHaveBeenCalled();
This doesn't work.
Others suggest using the window object
spyOn(window, 'thing').and.returnValue({});
But I'm in node :(.
When you use es6 modules instead CommonJS all exports are named so you may use:
export default (event) => {}
and then import & spy as
import * as SomeObj from 'emvio-util-responses'
...
beforeEach(() => {
spyOn(someObj, 'default')
});

Resources