I'm under the impression that jest.spyOn.mockImplementationOnce can mock a function as long as it is implemented as a function expression, but might fail to mock when the function is written as a function declaration. Why is it behaving like that?
I have one TS file in the folder */this_works with the following code:
export function myFunction() {
return myOtherFunction();
}
export const myOtherFunction = function() {
return 'something';
};
And then another TS file in the folder */this_doesnt with the following code:
export function myFunction() {
return myOtherFunction();
}
export function myOtherFunction() {
return 'something';
}
The only difference between them is that myOtherFunction is either a function declaration or a function expression.
Both files are submitted to the same test with jest.
import * as myModule from '../index';
describe('myFunction', () => {
it('does something', () => {
jest
.spyOn(myModule, 'myOtherFunction')
.mockImplementationOnce(
function() {
return 'hello jest';
},
);
const result = myModule.myFunction();
expect(result).toEqual('hello jest');
});
});
All code can be found at my playground repo's folder
The result is that the function expression is mocked and the test passes, while the function declaration is not mocked and the test fails.
Anecdotally, this seems to only happen if the function being mocked is not being called directly, instead it is being called by another function. When the declared function gets called directly, the test passes.
I would like to understand why that is so I can write better tests.
Related
Hi I have a function in typescript which returns nothing. When I try to import and call this function in another part of my app, I am getting errors. I am fairly new to typescript and am struggling with how to fix this issue.
Here's my code. I am trying to set up a few scripts to do some simple tests (would prefer not to use any testing frameworks).
My helper functions for testing are here
//File: helper.ts
type validator = () => void;
export const it = (desc: string, fn: validator) => {
try {
let res = fn();
console.log("\x1b[32m%s\x1b[0m", `\u2714 ${desc}`);
} catch (error) {
console.log("\n");
console.log("\x1b[31m%s\x1b[0m", `\u2718 ${desc}`);
console.error(error);
}
};
My tests use the helpers and are defined like this;
// File: dummytests.ts
import { strict as assert } from 'node:assert';
import { it } from "src/spec/helper";
export const check_if15_eqls_15 = it("shoulld check if something is true", () => {
assert.strictEqual(15, 15);
});
and i finally am running the tests as such;
// File: testRUnner.ts
import {check_if15_eqls_15} from 'src/spec/frontend/dummyTests';
console.log ("This test harness is for the frontend tests.\n");
check_if15_eqls_15();
this throws the error;
error TS2349: This expression is not callable.
Type 'void' has no call signatures.
5 check_if15_eqls_15();
~~~~~~~~~~~~~~~~~~
The line
export const check_if15_eqls_15 = it("shoulld check if something is true", () => {
assert.strictEqual(15, 15);
});
already calls the method.
This means check_if15_eqls_15 is already the return value of the method and unless the return value is not another function (which it isn't in this case), you can't call it again.
The same thing would happen in pure JS, since TypeScript does not change how the code is run
Something that might work for your example would be this:
export const check_if15_eqls_15 = () => it("shoulld check if something is true", () => {
assert.strictEqual(15, 15);
});
I have this function writeToFile which either appends or writes depending on the append argument. The appendFile and writeFile are from fs modules that I am trying to add to my unit tests.
The problem in the code below is while testing when I create stubs for fs.appendFile and fs.writeFile, the callCount is not incremented if I assign the append and write functions to a local variable func. If I just call these functions directly adding a conditional, the tests works fine but would be extra lines of code and ugly(according some people)
It would be nice if someone can explain this sinon's behavior with function assign to local variable and is there a solution without changing the function in utils.js
code snippet from utils.js:
export async function writeToFile(path, data, append) {
const func = append ? fs.appendFile : fs.writeFile;
return func(path, data, (err) => {
if (err) { logger.error(err); }
});
}
code snippet from test.js:
it('Write to file', async () => {
const append = sinon.stub(fs, 'appendFile');
const write = sinon.stub(fs, 'writeFile');
await writeToFile('path', 'buffer', false);
expect(append.callCount).to.equal(0);
expect(write.callCount).to.equal(1);
await writeToFile('path', 'buffer', true);
expect(append.callCount).to.equal(1);
expect(write.callCount).to.equal(1);
});
I can't figure out a way to stub a function called from within the same module this function is defined (the stub does not seem to work). Here's an example:
myModule.js:
'use strict'
function foo () {
return 'foo'
}
exports.foo = foo
function bar () {
return foo()
}
exports.bar = bar
myModule.test.js:
'use strict'
const chai = require('chai')
const sinon = require('sinon')
chai.should()
const myModule = require('./myModule')
describe('myModule', () => {
describe('bar', () => {
it('should return foo', () => {
myModule.bar().should.equal('foo') // succeeds
})
describe('when stubbed', () => {
before(() => {
sinon.stub(myModule, 'foo').returns('foo2') // this stub seems ignored
})
it('should return foo2', () => {
myModule.bar().should.equal('foo2') // fails
})
})
})
})
This reminds me of Java static functions which are not stubbable (almost).
Any idea how to achieve what I'm trying to do? I know that extracting foo in a different module will work, but that's not what I'm trying to do here. I'm also aware that invoking foo in the bar method with the keyword this will also work, I'm puzzled toward the use of ̀this in this context (since I'm not using OOP).
I just tested this. And it works like charm.
'use strict'
function foo () {
return 'foo';
}
exports.foo = foo;
function bar () {
return exports.foo(); // <--- notice
}
exports.bar = bar;
Explanation
when you do sinon.stub(myModule, 'foo').returns('foo2') then sinon stubs the exported object's foo not the actually foo function from inside your myModule.js ... as you must know, foo is in accessible from outside the module. So when you set exports.foo, the exported object exports.foo stores the ref of foo. and when you call sinon.stub(myModule, 'foo').returns('foo2'), sinon will stub exports.foo and not the actual foo
Hope this makes sense!
I was a bit wary of using exports since it's a bit magical (for instance when you're coding in Typescript, you never use it directly), so I'd like to propose an alternate solution, which still requires modifying the source code unfortunately, and which is simply to wrap the function to be stubbed into an object:
export const fooWrapper = {
foo() {...}
}
function bar () {
return fooWrapper.foo()
}
And sinon.stub(fooWrapper, 'foo'). It's a bit a shame having to wrap like that only for testing, but at least it's explicit and type safe in Typescript (contrary to to exports which is typed any).
This has bothered me for a while. I have two functions in the same file.
//fun.ts
export function fun1(){
let msg = fun2();
return msg;
}
export function fun2(): string{
return "Some message";
}
I have a typescript spec that stubs fun2 and calls fun1.
//fun.spec.ts
import * as Fun from 'fun';
describe('Stubing', () => {
it('should stub the return value', () => {
spyOn(Fun, 'fun2').and.returnValue("A different message");
expect(Fun.fun1()).toEqual("A different message")
});
});
But when I run the spec, the output I get is
Failures:
1) Stubing should stub the return value
1.1) Expected 'Some message' to equal 'A different message'.
I wrote the tests in typescript and then I have a gulp script that successfully transpiles and runs the jasmine specs. Everything works, the only thing that I can't figure out is why the spy is not working. An explanation would be appreciated.
I finally figured this out. In fun.ts, I am directly calling the fun2 object, but my Jasmine spec has no access to that object. The only object the Jasmine spec can access is the exports object. If I want the spy to work I need to call fun2 on the exports object.
//fun.ts
export function fun1(){
let msg = exports.fun2();
console.log(msg);
}
export function fun2(): string{
return "Some message";
}
Now when the spec executes I see
.
1 spec, 0 failures
I am writing some unit tests for node.js code and I use Sinon to stub function calls via
var myFunction = sinon.stub(nodeModule, 'myFunction');
myFunction.returns('mock answer');
The nodeModule would look like this
module.exports = {
myFunction: myFunction,
anotherF: anotherF
}
function myFunction() {
}
function anotherF() {
myFunction();
}
Mocking works obviously for use cases like nodeModule.myFunction(), but I am wondering how can I mock the myFunction() call inside anotherF() when called with nodeModule.anotherF()?
You can refactor your module a little. Like this.
var service = {
myFunction: myFunction,
anotherFunction: anotherFunction
}
module.exports = service;
function myFunction(){};
function anotherFunction() {
service.myFunction(); //calls whatever there is right now
}