I have a setup similar to this question but the partial mocking answer is not working for me.
I have a file 'FiscalYear.jsx' which contains a class FiscalYear and a few related non-class functions eg
export class FiscalYear {
constructor(fy) { //does stuff }
// other functionality
}
export function getFY(offset) {
// does stuff
return new FiscalYear(fy);
}
I want my Jest test for the getFY function to simply check that the FiscalYear constructor was called with the right parameter. I used partial mocking in my test file to accomplish this:
import { FiscalYear, getFY } from '../FiscalYear';
jest.mock('../FiscalYear', () => {
const originalModule = require.requireActual('../FiscalYear');
return {
...originalModule,
FiscalYear: jest.fn().mockImplementation((a) => { })
};
});
test("getFY", () => {
getFY(2);
expect(FiscalYear).toHaveBeenCalled();
});
I'll update the toHaveBeenCalled to toBeCalledWith once I make it actually call the function. However currently the test fails with
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
I put a console.log in the real constructor and confirmed that the getFY function is calling that instead of the mock constructor.
How to get this working? Is it a bad design to have these both in the same file? Totally new to JavaScript so if this is bad practice please let me know.
Related
I'm trying to stub a function using sinon. The function has the following signature
export function getIndexDocument(
svc: MetaHTTPService | ServiceConfig
): MetaPromise<RepoResponseResult<IndexDocument>> {
Is this the right way to sub it
sandbox.stub(getIndexDocument).resolves({} as RepoResponseResult)
I tried that but it returns an error.
Here's how this function is called.
I have a class called AssetsController with the following functions
public async exploreIndexDocument(): Promise<Asset | undefined> {
// it makes an HTTP request and returns a promise that resolves with the following info { repoId: "", assetId: "" }
const {
result: { assignedDirectories }
} = await getIndexDocument(this.serviceConfig).catch(err => {
throw new Error(`Bad repsonse`);
});
return {
repoId: result.repoId;
assetId: result.assetId
}
}
public async function copyAsset(asset) {
const res = await this.exploreIndexDocument();
const repoId = res.repoId;
return asset.copy(repoId);
}
I'm trying to test the function copyAsset, but it calls exploreIndexDocument which calls getIndexDocument. getIndexDocument is imported at the top of the file and lives in the module #ma/http.
getIndexDocument makes an HTTP request.
How can I test copyAsset given that it calls getIndexDocument which makes an HTTP request?
According to the docs, you can't stub an existing function.
You can:
// Create an anonymous sstub function
var stub = sinon.stub();
// Replaces object.method with a stub function. An exception is thrown
// if the property is not already a function.
var stub = sinon.stub(object, "method");
// Stubs all the object’s methods.
var stub = sinon.stub(obj);
What you can't do is stub just a function like:
var stub = sinon.stub(myFunctionHere);
This makes sense because if all you have is a reference to a function, then you can just create a new function to use instead, and then pass that into where ever your test needs it to go.
I think you just want:
const myStub = sandbox.stub().resolves({} as RepoResponseResult)
In your update it sounds like you want to put the stub on the AssetsController class. See this answer for more info on that, but in this case I think you want:
const myStub = sandbox
.stub(AssetsController.prototype, 'exploreIndexDocument')
.resolves({} as RepoResponseResult)
Now anytime an instance of AssetsController calls its exploreIndexDocument method, the stub should be used instead.
Playground
I think most of your problems can be solved by revisiting your architecture. For example, instead of creating an explicit dependency on getIndexDocument within your AssetController class you can simply inject it in. This will allow you to swap implementations depending on the context.
type IndexDocumentProvider = (svc: MetaHTTPService | ServiceConfig) => MetaPromise<RepoResponseResult<IndexDocument>>;
interface AssetControllerOptions {
indexDocumentProvider: IndexDocumentProvider
}
class AssetController {
private _getIndexDocument: IndexDocumentProvider;
public constructor(options: AssetControllerOptions) {
this._getIndexDocument = options.indexDocumentProvider;
}
}
Then you can use this._getIndexDocument wherever and not worry about how to make the original implementation behave like you want in your tests. You can simply provide an implementation that does whatever you'd like.
describe('copyAsset', () => {
it('fails on index document error.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.reject(new Error(':('));
});
....
});
it('copies asset using repo id.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.resolve({ repoId: "420" })
});
...
});
});
You can obviously use stubs instead of just functions or whatever if you need something fancy.
Above we removed an explicit dependency to an implementation and instead replaced it with a contract that must be provided to the controller. The is typically called Inversion of Control and Dependency Injection
So I am writing a test case for one of my function where I am making a call to another function of a library and I am trying to mock that function(saveCall), here is the sample code:
import { Call } from './somefolder/call';
class Demo {
var testIt = (params: any) => {
---- // Some other code
let call = new Call(params);
call.saveCall();
---- // Some other code
}
return {testIt: testIt};
}
And here is how I am writing unit test case for the same:
import { Call } from './somefolder/call';
var demo = new Demo();
test("Test it", () => {
let call = new Call({} as any);
let spyIt = jest.spyOn(call, 'saveCall').mockImplementation(()=>{console.log('here')});
demo.testIt();
expect(spyIt).toHaveBeenCalled(); // Throws error expect(jest.fn()).toHaveBeenCalled()
});
I am getting expect(jest.fn()).toHaveBeenCalled() error in expect, Now I feel I am getting error because the instance of call object in test file is different from what I have in Demo class and that is the reason spyOn doesn't know that whether the function has been called or not. I did try with mocking the entire Call.ts file but getting the same error.
Now my question is how can I create a mock and sucessfully test whether saveCall() has been called. Please note that I cannot change the implementation of testIt function.
Using jest.mock helper to mock Call class and assert on instance of the mocked class.
import { Call } from './somefolder/call';
import { Demo } from './Demo';
jest.mock('./somefolder/call'); // mock all named export items
describe("Demo", () => {
let demo: Demo;
let CallMocked: jest.Mock<Call>; // define type for mocked class
beforeEach(() => {
CallMocked = Call as any; // Actually, now Call is a mocked class
demo = new Demo();
});
test("Test it", () => {
demo.testIt();
expect(CallMocked.mock.instances[0].saveCall).toHaveBeenCalled(); // assert for mock instance
});
})
I'm testing code that instantiates an object from an external library. In order to make this testable, I've decided to inject the dependency:
Boiled down to:
const decorator = function (obj, _extLib) {
var ExtLib = _extLib || require('extlib')
config = determineConfig(obj) //This is the part that needs testing.
var el = new ExtLib(obj.name, config)
return {
status: el.pay({ amt: "one million", to: "minime" })
bar: obj.bar
}
}
In my test, I need to determine that the external library is instantiated with the proper config. I'm not interested in whether this external library works (it does) nor wether calling it, gives results. For the sake of the example, let's assume that on instantiating, it calls a slow bank API and then locks up millions of dollars: we want it stubbed, mocked and spied upon.
In my test:
it('instantiates extLib with proper bank_acct', (done) => {
class FakeExtLib {
constructor(config) {
this.acct = config.bank_acct
}
this.payMillions = function() { return }
}
var spy = sandbox.spy(FakeExtLib)
decorator({}, spy) // or, maybe decorator({}, FakeExtLib)?
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
Do note that testing wether e.g. el.pay() was called, works fine, using spies, in sinon. It is the instantiation with new, that seems untestable.
To investigate, let's make it simpler even, testing everything inline, avoiding the subject under test, the decorator function entirely:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
el = new ExtLib({ bank_acct: "1337" })
expect(el.acct).to.equal("1337")
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
The expect part passes. So apparently it is all called properly. But the sinon.assert fails. Still. Why?
How can I check that a class constructor is called with proper attributes in Sinon?" Is calledWithNew to be used this way? Should I spy on another function such as the ExtLib.prototype.constructor instead? If so, how?
You're really close.
In the case of your simplest example, you just need to create el using the spy instead of ExtLib:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
var el = new spy({ bank_acct: "1337" }) // use the spy as the constructor
expect(el.acct).to.equal("1337") // SUCCESS
sinon.assert.calledWithNew(spy) // SUCCESS
sinon.assert.calledWithExactly(spy, { bank_acct: "1337" }) // SUCCESS
done()
})
(Note that I modified the test to use calledWithExactly to check the arguments since calledWithNew doesn't seem to check the arguments properly in v7.2.2)
I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.
I am try to make a logging service for my TypeScript / Angular 2 App. Unfortunately if i call console.log the line number is wrong. Even if i try to return console.log().
Here is my code:
LoggerService.ts
export class LoggerService {
log(message) {
// Server-side logging
// [...]
if (clientSideLogging) return console.log(message);
}
}
SomewhereElse.ts
this.logger.log('hello world');
-> Shows line number of LoggerService.ts instead of source
You could use the .bind() method to bind window.console to your custom log method and then return the function so that the code is executed within the original scope when it is called.
In doing so, the line number will be preserved when calling the logger service's log method:
class LoggerService {
public log = console.log.bind(window.console);
}
// ...or annotated:
class LoggerService {
public log: (message) => void = console.log.bind(window.console);
}
Then if you want to add in your conditional statement:
class LoggerService {
public log = clientSideLogging ? console.log.bind(window.console) : () => {};
}
Here is an example with the compiled TypeScript code.
Aside from the one-liner solutions mentioned above, if you want to implement additional logic inside of the log method, then you could utilize a getter which will return and call the console.log function that is bound to window.console.
class LoggerService {
public get log (): Function {
// Implemnt server-side logging
return console.log.bind(window.console);
}
}
As you can tell, it is important for the console.log function to be returned since it will not preserve the line numbers when it is called directly within another scope.
Then if you want to add in your conditional statement:
class LoggerService {
public get log (): Function {
const log = console.log.bind(window.console);
// Implemnt server-side logging
return clientSideLogging ? log : () => {};
}
}
Here is an example with the compiled TypeScript code.
You could use .trace() instead of .log().
this.logger.trace('hello world');
This will give you a stack trace to the original line number.
https://developer.mozilla.org/en-US/docs/Web/API/Console/trace