Mocking/Stubbing `super` calls - javascript

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);
});
});
});

Related

How to spyOn an exported standalone function using javascript jest?

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

How to test class instance inside a function with Jest

I have the following hypothetical scenario:
// file MyClass.js in an external package
class MyClass {
myfunc = () => {
// do something
}
}
// file in my project
function myFunctionToBeTested() {
const instance = new MyClass()
instance.myFunc()
}
I need to create a test with Jest that makes sure instance.myFunc was called
One of the option is to replace MyClass module with mock implementation
const mockmyfunc = jest.fn()
jest.mock("path/to/external/package/MyClass", () => {
return jest.fn().mockImplementation(() => {
return {myfunc: mockmyfunc}
})
})
And then write following test
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
expect(mockmyfunc).toHaveBeenCalled()
})
Note that this is not the only way, you can dive into https://facebook.github.io/jest/docs/en/es6-class-mocks.html for other alternatives.
Update
If the myfunc would be an actual function (which i guess is not an option since it's external package?)
export class MyClass {
myFunc() {
// do smth
}
}
and you would not need to replace the implementation, you could be using jest's automock
import MyClass from "path/to/external/package/MyClass"
jest.mock("path/to/external/package/MyClass")
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
const mockMyFunc = MyClass.mock.instances[0].myFunc
expect(mockMyFunc).toHaveBeenCalled()
})
you can mock out the class and assign the default export of that file to a variable as follows:
jest.mock('../../utils/api/api');
const FakeClass = require('../someFile.js').default;
then access calls to a function on your mock class like this:
FakeClass.prototype.myFunc.mock.calls

ES6 import nested function - mocha

I am using ES6, and I want to start testing using mocha & chai.
My current test file code is :
const assert = require('chai').assert;
var app = require('../../../../src/app/login/loginController').default;
describe('login Controller tests', function(){
it('no idea ', function(){
let result = app();
assert.equal(result, 'hello');
})
})
and my loginController.js is :
class LoginController {
checkout(){
return 'hello';
}
}
export default LoginController
I want to import the 'checkout' function into a variable inside my test file, but so far I am able to import only the class.
Will appreciate any help, thanks !
You cannot import methods directly from classes. If you want to import a function without a class as intermediary, then you need to define the function outside the class. Or if you really meant checkout to be an instance method, then you need to call it on an instance.
Here's an example file derived from yours:
export class LoginController {
// Satic function
static moo() {
return "I'm mooing";
}
// Instance method
checkout() {
return "hello";
}
}
// A standalone function.
export function something() {
return "This is something!";
}
And a test file that exercises all functions, adapted from the file you show in your question:
const assert = require('chai').assert;
// Short of using something to preprocess import statements during
// testing... use destructuring.
const { LoginController, something } = require('./loginController');
describe('login Controller tests', function(){
it('checkout', function(){
// It not make sense to call it without ``new``.
let result = new LoginController();
// You get an instance method from an instance.
assert.equal(result.checkout(), 'hello');
});
it('moo', function(){
// You get the static function from the class.
assert.equal(LoginController.moo(), 'I\'m mooing');
});
it('something', function(){
// Something is exported directly by the module
assert.equal(something(), 'This is something!');
});
});

Run chained method before anything in constructor?

I have this simple class:
class Foo {
constructor() {
this.init();
return this;
}
init() {
this.onInit();
}
onInit(callback) {
this.onInit = () => callback();
return this;
}
}
new Foo().onInit(() => console.log('baz'));
It's obviously flawed, because it will call init before the onInit method is able to define the onInit property/callback.
How can I make this work without change the interface?
How can I make this work without change the interface?
You can't, the interface is inherently flawed. That's really the answer to your question.
Continuing, though, with "what can I do instead":
If you need to have a callback called during initialization, you need to pass it to the constructor, not separately to the onInit method.
class Foo {
constructor(callback) {
this.onInit = () => {
callback(); // Call the callback
return this; // Chaining seemed important in your code, so...
};
// Note: Constructors don't return anything
}
}
new Foo(() => console.log('baz'));
In a comment you've said:
I see your point, the fact is that my library is new Something().onCreate().onUpdate()
It sounds like you might want to adopt the builder pattern instead:
class Foo {
constructor(callbacks) {
// ...use the callbacks here...
}
// ...
}
Foo.Builder = class {
constructor() {
this.callbacks = {};
}
onCreate(callback) {
this.callbacks.onCreate = callback;
}
onUpdate(callback) {
this.callbacks.onUpdate = callback;
}
// ...
build() {
// Validity checks here, do we have all necessary callbacks?
// Then:
return new Foo(this.callbacks);
}
};
let f = new Foo.Builder().onCreate(() => { /*...*/}).onUpdate(() => { /*... */}).build();
...although to be fair, a lot of the advantages (though not all) of the builder pattern can be realized in JavaScript by just passing an object into constructor directly and doing your validation there, e.g.:
let f = new Foo({
onCreate: () => { /*...*/},
onUpdate: () => { /*...*/}
});
Assuming that onInit is supposed to be some sort of hook to be called synchronously whenever an object is instantiated, you can't solve this on the instance level.
You can make onInit a static function, like so:
class Foo {
constructor() {
// whatever
Foo.onInit();
}
static onInit() {} // empty default
}
Foo.onInit = () => console.log('baz'); // Override default with your own function
const f = new Foo();
const f2 = new Foo();

ES2016 Class, Sinon Stub Constructor

I'm trying to stub out a super call with sinon, and es2016 but I'm not having much luck. Any ideas why this isn't working?
Running Node 6.2.2, this might be an issue with its implementation of classes/constructors.
.babelrc file:
{
"presets": [
"es2016"
],
"plugins": [
"transform-es2015-modules-commonjs",
"transform-async-to-generator"
]
}
Test:
import sinon from 'sinon';
class Foo {
constructor(message) {
console.log(message)
}
}
class Bar extends Foo {
constructor() {
super('test');
}
}
describe('Example', () => {
it('should stub super.constructor call', () => {
sinon.stub(Foo.prototype, 'constructor');
new Bar();
sinon.assert.calledOnce(Foo.prototype.constructor);
});
});
Result:
test
AssertError: expected constructor to be called once but was called 0 times
at Object.fail (node_modules\sinon\lib\sinon\assert.js:110:29)
at failAssertion (node_modules\sinon\lib\sinon\assert.js:69:24)
at Object.assert.(anonymous function) [as calledOnce] (node_modules\sinon\lib\sinon\assert.js:94:21)
at Context.it (/test/classtest.spec.js:21:18)
Note: this issue seems to only happen for constructors. I can spy on methods inherited from the parent class without any issues.
You'll need to setPrototypeOf the subClass due to the way JavaScript implements inheritance.
const sinon = require("sinon");
class Foo {
constructor(message) {
console.log(message);
}
}
class Bar extends Foo {
constructor() {
super('test');
}
}
describe('Example', () => {
it('should stub super.constructor call', () => {
const stub = sinon.stub().callsFake();
Object.setPrototypeOf(Bar, stub);
new Bar();
sinon.assert.calledOnce(stub);
});
});
You need to spy instead of stub,
sinon.spy(Foo.prototype, 'constructor');
describe('Example', () => {
it('should stub super.constructor call', () => {
const costructorSpy = sinon.spy(Foo.prototype, 'constructor');
new Bar();
expect(costructorSpy.callCount).to.equal(1);
});
});
*****Update******
Above was not working as expected, I added this way and is working now.
describe('Example', () => {
it('should stub super.constructor call', () => {
const FooStub = spy(() => sinon.createStubInstance(Foo));
expect(FooStub).to.have.been.calledWithNew;
});
});
Adding to the accepted answer of Wenshan, there is one step that may be overlooked when stubbing the parent class and replacing the original parent class with setPrototypeOf.
💡 Additionally, to avoid it breaking the succeeding tests it is a good idea to set back the original parent class at the end, like:
const sinon = require("sinon");
class Foo {
constructor(message) {
console.log(message);
}
}
class Bar extends Foo {
constructor() {
super('test');
}
}
describe('Bar constructor', () => {
it('should call super', () => {
const stub = sinon.stub().callsFake();
const original = Object.getPrototypeOf(Bar); // Bar.__proto__ === Foo
Object.setPrototypeOf(Bar, stub); // Bar.__proto__ === stub
new Bar();
sinon.assert.calledOnce(stub);
Object.setPrototypeOf(Bar, original); // Bar.__proto__ === Foo
});
});
The addition is
// saving the reference to the original parent class:
const original = Object.getPrototypeOf(Bar);
// setting back the original parent class after stubbing and the assertion:
Object.setPrototypeOf(Bar, original);
It doesn’t work for me either. I managed a workaround that works for me, i use spy as well:
class FakeSchema {
constructor(newCar) {
this.constructorCallTest();
this.name = newCar.name;
}
constructorCallTest() {
mochaloggger.log('constructor was called');
}
}
// spy that tracks the contsructor call
var fakeSchemaConstrSpy = sinon.spy(FakeCarSchema.prototype,'constructorCallTest');
Hope that was helpful
If you are in a browser environment, the following works too:
let constructorSpy = sinon.spy(window, 'ClassName');
For example this would work with Jasmine.
Mocha instead runs in Node environment, there is no window. The variable you'd be looking for is global

Categories

Resources