I have a really simple JS lib (called trysinon.js) that looks like this:
export function foo() {
bar();
}
export function bar() {
return 2;
}
And I have the following test
import expect from 'expect';
import sinon from 'sinon';
import * as trysinon from 'trysinon';
describe('trying sinon', function() {
beforeEach(function() {
sinon.stub(trysinon, 'bar');
});
afterEach(function() {
trysinon.bar.restore();
});
it('calls bar', function() {
trysinon.foo();
expect(trysinon.bar.called).toBe(true);
});
});
And the test is failing. How can I ensure the test passes?
Because in foo(), you called bar() which is inner function of trysinon.js. This bar() is different with the bar() you stubbed which is exported. The best way is to change trysinon to class, or called exported bar() in foo() as following.
function bar() { return 2; }
module.exports.bar = bar;
function foo() {
module.exports.bar();
}
module.exports.foo = foo;
then you can stub bar() with sinon.stub(trysinon, 'bar').returns(2)
Hope this can help you.
I use arrow function instead, and it works.
export const foo = () => {
bar();
}
export const bar = () => {
return 2;
}
Related
It is a very simple scenario but I've struggled to find an answer for it.
helpers.ts:
export function foo() {
bar();
}
export function bar() {
// do something
}
helpers.spec.ts:
import { foo, bar } from "./helpers";
describe("tests", () => {
it("example test", () => {
const barSpy = // how can i set this up?
foo();
expect(barSpy).toHaveBeenCalled();
});
});
I can't do const spy = jest.spyOn(baz, 'bar'); because I don't have a module/class to put in place of "baz". It is just an exported function.
Edit:
Jest mock inner function has been suggested as a duplicate but unfortunately it doesn't help with my scenario.
Solutions in that question:
Move to separate module: I cannot do this for my scenario. If I am testing every function in my application, this would result in me creating 10s of new files which is not ideal. (To clarify, I think this solution would work but I cannot use it for my scenario. I am already mocking a separate file function successfully in this test file.)
Import the module into itself:
helpers.spec.ts:
import * as helpers from "./helpers";
describe("tests", () => {
it("example test", () => {
const barSpy = jest.spyOn(helpers, 'bar');
foo();
expect(barSpy).toHaveBeenCalled();
});
});
results in:
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
This is the closed solution:
export function bar() {
// do something
}
export function foo() {
exports.bar(); // <-- have to change to exports.bar() instead of bar()
// or this.bar(); would also work.
}
import * as utils from './utils';
describe('tests', () => {
it('example test', () => {
const barSpy = jest.spyOn(utils, 'bar');
utils.foo();
expect(barSpy).toHaveBeenCalled();
});
});
Or take a look this duplicated question
I am trying to run a local function, but I want to fire it from inside an imported module. This would be a sketch of how it should work... Any suggestions?
index.js
import { runBar } from "myModule.js";
runBar();
function foo() {
console.log('foo should run whenever bar is executed');
}
myModule.js
export function runBar(){
bar();
}
function bar() {
console.log("bar is running...");
//I want to call foo from here...
}
What about passing foo as a parameter?
index.js
import { runBar } from "myModule.js";
runBar(foo);
function foo() {
console.log('foo should run whenever bar is executed');
}
myModule.js
export function runBar(foo){
bar(foo);
}
function bar(foo) {
console.log("bar is running...");
//I want to call foo from here...
foo();
}
You can't call foo from bar, because foo isn't in scope.
To run foo, your options are:
Pass it to runBar and have runBar pass it to bar; or
Export foo and have myModule.js import it (cyclic dependencies are okay); or
Make foo a global (globalThis.foo = function foo() {/*...*/}, or possibly with window instead of globalThis), but I don't recommend doing that.
I have a file called something.js with this code:
exports.run = (args) => {
function foo() {
console.log("foo");
}
}
Now I want to call foo from another file.
My code looks like this with errors attached as comments:
const something = require("./something.js");
module.exports = {
callFoo: function() {
something.foo(); //TypeError: something.foo is not a function
something.run.foo(); //TypeError: something.run.foo is not a function
}
Is it possible to call such a function which is apparently not a function?
You assign an arrow function to exports.run and in that function, you define the foo function, but this function is not reachable from outside of the arrow function. Yur code is nearly equivalent to:
exports.run = function(args) {
function foo() {
console.log("foo");
}
}
You most likely want to write:
exports.run = {
foo() {
console.log("foo");
}
}
Or
exports.run = {
foo : function() {
console.log("foo");
}
}
I want to keep this in class methods.
I can use arrow functions, but I want to override some methods in extended class.
Now I have this solution and it works:
class Foo {
bar = "Context preserved.";
constructor() {
this.foo = this.foo.bind(this);
}
foo() {
alert(this.bar);
}
}
class Foo2 extends Foo {
foo() {
alert(this.bar + " Class extended");
}
}
class Bar {
bar = "Context lost.";
}
let foo = new Foo2();
let bar = new Bar();
foo.foo.apply(bar); // Context preserved. Class extended
Is it a good practice to do it such way? If it is, is there some keyword in typescript to do it automatically?
like
class Foo() {
public conserved foo() { }
}
which generates:
var Foo = (function () {
function Foo() {
this.foo = this.foo.bind(this);
}
Foo.prototype.foo = function () { };
return Foo;
}());
It's a valid practice and it's being used.
I'm unaware of a way to tell typescript to do this automatically, but you can search the issues for something like it.
You can have a decorator that does that for you, for example:
function construct(constructor: Function, methods: string[], args: any[]) {
var c: any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
let instance = new c();
methods.forEach(name => {
instance[name] = instance[name].bind(instance);
});
return instance;
}
function BindMethods(constructor: Function) {
const methods = [] as string[];
Object.keys(constructor.prototype).forEach(name => {
if (typeof constructor.prototype[name] === "function") {
methods.push(name);
}
});
return (...args: any[]) => {
return construct(constructor, methods, args);
};
}
#BindMethods
class Foo {
bar = "Context preserved.";
foo() {
console.log(this.bar);
}
}
let foo = new Foo();
setTimeout(foo.foo, 10);
(code in playground)
I tested it with this simple use case and it worked just fine.
I would like to mock out super calls, especially constructors in some ES6 classes. For example
import Bar from 'bar';
class Foo extends Bar {
constructor(opts) {
...
super(opts);
}
someFunc() {
super.someFunc('asdf');
}
}
And then in my test, I would like to do something like
import Foo from '../lib/foo';
import Bar from 'bar';
describe('constructor', function() {
it('should call super', function() {
let opts = Symbol('opts');
let constructorStub = sinon.stub(Bar, 'constructor');
new Foo(opts);
sinon.assert.calledWith(constructorStub, opts);
});
})
describe('someFunc', function() {
it('should call super', function() {
let funcStub = sinon.stub(Bar, 'someFunc');
let foo = new Foo(opts);
foo.someFunc();
sinon.assert.calledWith(funcStub, 'asdf');
});
})
Figured it out, and #Bergi was on the right track.
In reponse to #naomik's question - My main purpose for wanting to stub this out was two fold. First, I didn't want to actually instantiate the super class, merely validate that I was calling the proper thing. The other reason (which didn't really come through since I was trying to simplify the example), was that what I really cared about was that I was doing certain things to opts that I wanted to make sure were carried through properly to the super constructor (for example, setting default values).
To make this work, I needed to dosinon.stub(Bar.prototype, 'constructor');
This is a better example and working test.
// bar.js
import Memcached from 'memcached'
export default class Bar extends Memcached {
constructor(opts) {
super(opts);
}
}
// foo.js
import Bar from './bar.js';
export default class Foo extends Bar {
constructor(opts) {
super(opts);
}
}
// test.js
import Foo from './foo.js';
import Bar from './bar.js';
import Memcached from 'memcached';
import sinon from 'sinon';
let sandbox;
let memcachedStub;
const opts = '127.0.0.1:11211';
describe('constructors', function() {
beforeEach(function() {
sandbox = sinon.sandbox.create();
memcachedStub = sandbox.stub(Memcached.prototype, 'constructor');
});
afterEach(function() {
sandbox.restore();
});
describe('#bar', function() {
it('should call super', function() {
new Bar(opts);
sinon.assert.calledOnce(memcachedStub);
sinon.assert.calledWithExactly(memcachedStub, opts);
});
});
describe('#foo', function() {
it('should call super', function() {
new Foo(opts);
sinon.assert.calledOnce(memcachedStub);
sinon.assert.calledWithExactly(memcachedStub, opts);
});
});
});