jest support for mocking javascript 'classes' - javascript

I'm on a project that uses jest as the testing framework for a nodejs server. On one controller (called ControllerImTesting), it news up an instance of a helper class (we'll call ClassIWantToMock and uses it for the controller. I already have many tests written around ClassIWantToMock itself so naturally when I test ControllerImTesting, I just want to mock ClassIWantToMock out.
It's pretty simple, I've created another js file in __mocks__ that contains a dumb version of ClassIWantToMock. When I new it up in ControllerImTesting, I want it to use the dumb version in __mocks__.
I've tried lots of configurations at this point but my desperation move is to use setMock like:
jest.setMock('/path/to/real/class/I/want/to/mock', '/path/to/dumb/version') How could this fail?
But when running the test, I get TypeError: ClassIWantToMock is not a constructor.
I look at my dumb version of ClassIWantToMock and change the style from something like const ClassIWantToMock = (req) => { to class ClassIWantToMock {. I get the same error which kind of makes sense because using the es6 class style is just syntactic sugar.
Just to confirm, in my real controller, I write a line, console.log(ClassIWantToMock) above the line where it news up the instance. It indeed prints out the '/path/to/dumb/version'. It is trying to mock it but cannot.
Is this a limitation of Jest? Or am I simply not using it correctly? --> How should this be done?
UPDATE
./ClassIWantToMock.js
class ClassIWantToMock {
constructor(stuff) {
this.stuff = stuff
}
doStuff() {
console.log('some real stuff')
}
}
module.exports = ClassIWantToMock
./__mocks__/ClassIWantToMock.js
class ClassIWantToMock {
constructor(fakeStuff) {
this.fakeStuff = fakeStuff
}
doStuff() {
console.log('some fake stuff')
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.js
const ClassIWantToMock = require('./ClassIWantToMock')
class ControllerImTesting {
static aMethod(req, res, next) {
const helper = ClassIWantToMock('real stuff')
helper.doStuff()
return next()
}
}
module.exports = ClassIWantToMock
./ControllerImTesting.spec.js
jest.setMock('./ClassIWantToMock', './__mocks__/ClassIWantToMock')
const ControllerImTesting = require('./ControllerImTesting')
describe('basic test', () => {
test('should work', () => {
return ControllerImTesting.aMethod({}, {}, () => {}).then(() => {
// expect console to display 'some fake stuff'
})
})
})

Related

Jest + React: IntersectionObserver mock not working?

In my component.test.js, I tried mocking the IntersectionObserver by doing something like this:
const mock = ()=>({
observe: jest.fn(),
disconnect: jest.fn()
});
window.IntersectionObserver = jest.fn().mockImplementation(mock);
describe("Component", ()=>{
it("test 1", ()=>{
render(<Component/>);
...
}
});
My component.js looks something like this (it does that infinite pagination thing):
//ref to last item loaded >> load more items once it enters view
const observer = useRef();
const lastEntryRef = useCallback((node)=>{
...
observer.current.disconnect(); //ERROR LINE
}
...
);
When I run the tests, however, I get TypeError: observer.current.disconnect is not a function; same goes for observer.current.observe() if it runs. I tried testing it inside the it() block of component.test.js itself by instantiating an IntersectionObserver and then calling those methods and the same message showed when I re-ran the tests, so the errors look unrelated to how IntersectionObserver was set up in component.js. Am I not mocking IntersectionObserver correctly? If so, how do I fix it?
I recommend you to replace the arrow function for a normal function because you need to use the new operator to create an InterceptionObserver object:
const mock = function() {
return {
observe: jest.fn(),
disconnect: jest.fn(),
};
};
//--> assign mock directly without jest.fn
window.IntersectionObserver = mock;
The you can check if window.InterceptionObserver.observe has been called.

Import function into jest.mock to avoid boilerplate code

I assume this must be a pretty straightforward solution, but I am struggling to find a solution.
I have a function at the top of my tests:
jest.mock('../someFile', () => {
const setWidth = () => {
// lots of complex logic
};
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
So, I understand that jest.mock is hoisted above the import statements in every test run, but say I would like to cut down on the boiler plate code I need in this file and as an example if setWidth was a really big function and I want to import it from another file, is there any way I could do this?
If I move setWidth to another file and try the following it fails due to the hoisting
import { setWidth } from ./setWidth
jest.mock('../someFile', () => {
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
The error received is:
● Test suite failed to run
Invalid variable access: setWidth
Thanks in advance for any possible solutions!
jest.mock gets hoisted above the import, so that won't work. But what you can do is use requireActual
jest.mock('../someFile', () => {
const { setWidth } = jest.requireActual('./setWidth');
return {
width: jest
.fn()
.mockImplementation(element => {
setWidth(element);
};
};
};
};
Looks like you’re starting to go a bit crazy with "testing infrastructure" though - try to consider if there's a more "real" way (less testing infrastructure) you can test your code.
The most likely way to do this is to break the code your testing down into smaller functions/components.

How can I get a property attached to a global to stub properly with sinon?

I have a helper.js that loads before my tests:
before(async function() {
this.timeout(30000)
global.db = require(`${process.cwd()}/models`)()
...
Then in my test, I have:
describe.only('Phone Library', () => {
let updateCallSpy
beforeEach(() => {
sinon.stub(twilioClient.calls, 'create').resolves({})
updateCallSpy = sinon.stub(global.db.models.Call, 'update').resolves(true)
// sinon.stub(global.db.models.Conversation, 'update').resolves({})
})
The twilioClient.calls.create stubs properly. But the global.db.models.Call.update does not.
In my actual code, I'm using it like:
await global.db.models.Call.update({ status: newCallStatus }, { where: { id: foundConversation.Call.id } })
When I console.log(global.db.models.Call), it simply outputs Call. However, the .update function is there and does what it's supposed to do (a Sequelize model that updates).
I'm sure It's something terribly obvious, so any pointers would be greatly appreciated. Thanks.
The sequelize model methods are defined as prototype by the sequelize core.
The following should work
updateCallSpy = sinon.stub(global.db.models.Call.prototype, 'update').resolves(true)
You can also create a stub instance:
updateCallStubInstance = sinon.createStubInstance(global.db.models.Call)

Angular - Unit testing Subject()?

I have a Angular service that simply uses a Subject, but I am unsure of how to write unit tests for it.
I saw [this thread][1], but I didn't find it terribly helpful.
I have attempted to mock next() but I am more than a little lost.
You should spy on service.serviceMsg and not service, because next() method appears to be on serviceMsg subject.
it('should catch what is emitted', () => {
const nextSpy = spyOn(service.serviceMsg, 'next');
service.confirm(ACTION);
expect(nextSpy).toHaveBeenCalled();
});
EDIT :
You should also change the way you are creating service instance. What you show in your code is applicable only for component instance creation
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MessageService]
});
service = TestBed.get(MessageService); // get service instance
httpMock = TestBed.get(HttpTestingController);
});
Firstly you can just subscribe to your Subject and inside expect some value and after that just execute method which will emit that:
it('should catch what is emitted', () => {
service.serviceMsg.subscribe(msg => {
expect(msg).toEqual(something);
});
service.confirm(value); // this should match what you expect above
});

How to stub ES6 node_modules when using import?

I am a bit confused while writing tests. My stack is mocha, chai and sinon + babel to transpile. And recently I've started to use ES6 imports and exports. It's working great so far, but I have trouble with mocking some dependencies. Here is my case:
service.js
import {v4} from 'uuid';
function doSomethingWithUuid() {
return v4();
}
export function doSomething() {
const newUuid = doSomethingWithUuid();
return newUuid;
}
service.test.js
import {doSomething} from './service';
describe('service', () => {
it('should doSomething' () => {
// how to test the return of doSomething ?
// I need to stub v4 but I don't know how...
});
});
Things I've considered: sinon.stub, but I have not managed to make it work. Trying to import all uuid with import * as uuid from 'uuid'. But within my service.js, it's still
the original function which is called...
Plus as imports are supposed to be read-only, once it'll be native, this solution wouldn't work...
The only interesting thing I found on the net was this solution, to add a function in my service in order to let the outside world to override my dependencies.
(see https://railsware.com/blog/2017/01/10/mocking-es6-module-import-without-dependency-injection/).
import * as originalUuid from 'uuid';
let {v4} = originalUuid;
export function mock(mockUuid) {
({v4} = mockUuid || originalUuid);
}
This is ok to write this little boilerplate code, but it troubles me to add it in my code... I would prefer to write boilerplate in my test or some config. Plus, I don't want to
have an IoC container, I want to keep my functions as little as possible and stay as fonctional as I can...
Do you have any ideas? :)
You should be able to use a module like proxyquire for this. This is not tested code, but it would go something like the following:
const proxyquire = require('proxyquire');
const uuidStub = { };
const service = proxyquire('./service', { uuid: uuidStub });
uuidStub.v4 = () => 'a4ead786-95a2-11e7-843f-28cfe94b0175';
describe('service', () => {
it('should doSomething' () => {
// doSomething() should now return the hard-coded UUID
// for predictable testing.
});
});

Categories