How to test declared function - javascript

I'm trying to make a test on FiveM JavaScript codes using Sinon. Is there any workaround for "declared function"? Or can Sinon just skip the "declared function"?
The codes below are the codes on the node_modules file
declare function RegisterCommand(commandName: string, handler: Function, restricted: boolean): void;
I already make a test command like this
const testCommandHandler = (source, args, raws) => {
console.log('source', source, 'args', args[0], raws);
}
RegisterCommand('test', testCommandHandler, false);
module.exports = {
testCommandHandler
}
And already tried test code like the code below
describe('#server', function () {
const sandbox = sinon.createSandbox();
before(() => {
global.window = {
RegisterCommand: sandbox.stub()
}
})
it('should call testCommand', function () {
testCommandHandler(123, ['test', 'command'], 'test command');
});
});
But when I tried to run the test it failed with a message
ReferenceError: RegisterCommand is not defined
Edit:
I have found the solution.. I just move the handler function to another file and test that file. Works like a charm.

Related

Matcher error: received value must be a mock or spy function

I'm writing tests (with Jest and React Testing Library) for a form React component. I have a method that runs on form submit:
const onSubmit = (data) => {
// ...
setIsPopupActive(true);
// ...
};
and useEffect that runs after isPopupActive change, so also on submit:
useEffect(() => {
if (isPopupActive) {
setTimeout(() => {
setIsPopupActive(false);
}, 3000);
}
}, [isPopupActive]);
In the test, I want to check, whether the popup disappears after 3 seconds. So here's my test:
it('Closes popup after 3 seconds', async () => {
const nameInput = screen.getByPlaceholderText('Imię');
const emailInput = screen.getByPlaceholderText('Email');
const messageInput = screen.getByPlaceholderText('Wiadomość');
const submitButton = screen.getByText('Wyślij');
jest.useFakeTimers();
fireEvent.change(nameInput, { target: { value: 'Test name' } });
fireEvent.change(emailInput, { target: { value: 'test#test.com' } });
fireEvent.change(messageInput, { target: { value: 'Test message' } });
fireEvent.click(submitButton);
const popup = await waitFor(() =>
screen.getByText(/Wiadomość została wysłana/)
);
await waitFor(() => {
expect(popup).not.toBeInTheDocument(); // this passes
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 3000);
});
});
However, I'm getting the error:
expect(received).toHaveBeenCalledTimes(expected)
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function setTimeout]
What am I doing wrong?
Jest 27 has breaking changes for fakeTimers. It seems Jest contributors doesn't update documentation on time. This comment on Github issues confirms it. Moreover, here related PR.
Well, you can solve your problem by two ways.
Configure Jest to use legacy fake timers. In jest.config.js you can add line (but it not works for me):
module.exports = {
// many of lines omited
timers: 'legacy'
};
Configure legacy fake timers for individually test suite, or even test:
jest.useFakeTimers('legacy');
describe('My awesome logic', () => {
// blah blah blah
});
It's preferably to use new syntax based on #sinonjs/fake-timers. But I can't find working example for Jest, so I'll update this answer as soon as possible.
The below approach worked
beforeEach(() => {
jest.spyOn(global, 'setTimeout');
});
afterEach(() => {
global.setTimeout.mockRestore();
});
it('Test if SetTimeout is been called', {
global.setTimeout.mockImplementation((callback) => callback());
expect(global.setTimeout).toBeCalledWith(expect.any(Function), 7500);
})
In your case setTimeout is not a mock or spy, rather, it's a real function. To make it a spy, use const timeoutSpy = jest.spyOn(window, 'setTimeout'). And use timeoutSpy in the assertion.
You could also test not the fact of calling the setTimeout function, but assert that setIsPopupActive was called once, and with false. For this you might need to do jest.runOnlyPendingTimers() or jest.runAllTimers()

Difference between resetAllMocks, resetModules, resetModuleRegistry, restoreAllMocks in Jest

I'm trying to wrap my head around the following in Jest:
resetAllMocks, resetModules, resetModuleRegistry and restoreAllMocks
and I'm finding it difficult.
I read the jest documentation but it's not too clear. I would appreciate it if someone can please provide me with an example of how the above work and they are different from each other.
The following sections explain the behaviors of each function and its corresponding config directive. In the case of config directives, the explained behavior takes place in between each test making them more and more isolated from the other tests.
References to fn are implying a sample jest mock function under each of these actions.
jest.clearAllMocks() and clearMocks:[boolean]
Resets all the mocks usage data, not their implementation. In other words, it only replaces fn.mock.calls and fn.mock.instances properties of a jest mock function.
jest.resetAllMocks() and the resetMocks:[boolean]
A superset of clearAllMocks() which also takes care of resetting the implementation to a no return function. In other words, it will replace the mock function with a new jest.fn(), not just its fn.mock.calls and fn.mock.instances.
jest.restoreAllMocks() and restoreMocks:[boolean]
Similar to resetAllMocks(), with one very important difference. It restores the original implementation of "spies". So, it goes like "replace mocks with jest.fn(), but replace spies with their original implementation".
So, in cases where we manually assign things with jest.fn() (not spies), we have to take care of implementation restoration ourselves as jest won't be doing it.
jest.resetModules() and resetModules:[boolean]
It resets Jest's module registry which is a cache for all required/imported modules. Jest will re-import any required module after a call to this. Imagine a clean slate without having to deal with all the mocked out modules in other tests.
jest.resetModuleRegistry
It's just an alias for resetModules, see:
https://github.com/facebook/jest/blob/7f69176c/packages/jest-runtime/src/index.ts#L1147
See how clearing, resetting and restoring differ in action:
https://repl.it/#sepehr/jest-mock-api-reset-restore#jest-mock-apis.test.js
PASS ./jest-mock-apis.test.js
jest mock reset/restore api
when calling mockReset() on a test double with custom impl.
if the test double is a spy
✓ jest replaces the impl. to a new undefined-returning jest.fn() (18ms)
if the test double is "not" a spy
✓ jest replaces the impl. to a new undefined-returning jest.fn() (17ms)
when calling mockRestore() on a test double with custom impl.
if the test double is "not" a spy
✓ jest resets the impl. to a new undefined-returning jest.fn() (2ms)
if the test double is a spy
✓ jest restores the original impl. of that spy (7ms)
describe('jest mock reset/restore api', () => {
describe('when calling mockReset() on a test double with custom impl.', () => {
describe('if the test double is a spy', () => {
test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
const module = { api: () => 'actual' }
jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
expect(module.api()).toStrictEqual('spy mocked')
expect(module.api).toHaveBeenCalledTimes(1)
module.api.mockReset()
expect(module.api()).toStrictEqual(undefined)
expect(module.api).toHaveBeenCalledTimes(1)
})
})
describe('if the test double is "not" a spy', () => {
test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
const api = jest.fn(() => 'non-spy mocked')
expect(api()).toStrictEqual('non-spy mocked')
expect(api).toHaveBeenCalledTimes(1)
api.mockReset()
expect(api()).toStrictEqual(undefined)
expect(api).toHaveBeenCalledTimes(1)
})
})
})
describe('when calling mockRestore() on a test double with custom impl.', () => {
describe('if the test double is "not" a spy', () => {
test('jest resets the impl. to a new undefined-returning jest.fn()', () => {
const api = jest.fn(() => 'non-spy mocked')
expect(api()).toStrictEqual('non-spy mocked')
expect(api).toHaveBeenCalledTimes(1)
api.mockRestore()
expect(api()).toStrictEqual(undefined)
expect(api).toHaveBeenCalledTimes(1)
})
})
describe('if the test double is a spy', () => {
test('jest restores the original impl. of that spy', () => {
const module = { api: () => 'actual' }
jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
expect(module.api()).toStrictEqual('spy mocked')
expect(module.api).toHaveBeenCalledTimes(1)
module.api.mockRestore()
expect(module.api()).toStrictEqual('actual')
expect(module.api).not.toHaveProperty('mock')
})
})
})
})
Thanks for #sepehr answer.
I think it would be easier to understand by example.
Quick tips:
If you want to test mock function called times, clear before you use
If you want to make sure mock return value wouldn't pollute other test case, call reset
If you want to use origin method instead of mock implementation, call restore.
import {Calculator} from './calculator';
describe('calculator add', function () {
let calculator = new Calculator();
const mockAdd = jest.spyOn(calculator, 'add');
it('mock the add method', function () {
calculator.add = mockAdd.mockReturnValue(5);
expect(calculator.add(1, 2)).toBe(5);
});
it('because we didnt clear mock, the call times is 2', function () {
expect(calculator.add(1, 2)).toBe(5);
expect(mockAdd).toHaveBeenCalledTimes(2);
});
it('After clear, now call times should be 1', function () {
jest.clearAllMocks();
expect(calculator.add(1, 2)).toBe(5);
expect(mockAdd).toHaveBeenCalledTimes(1);
});
it('we reset mock, it means the mock has no return. The value would be undefined', function () {
jest.resetAllMocks();
expect(calculator.add(1, 2)).toBe(undefined);
});
it('we restore the mock to original method, so it should work as normal add.', function () {
jest.restoreAllMocks();
expect(calculator.add(1, 2)).toBe(3);
});
});

Jest how to modify the global object value for each unit test

I'm writing unit tests that require the window.location.href to be defined
The first unit test is created as follows
describe('myMethod()', () => {
beforeEach(() => {
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
configurable: true,
value: {
href: 'http://localhost:7777/mainPoint?param=val1',
},
});
});
it('should call with val1', () => {
myMethod();
expect(myService.detectURLCall).toHaveBeenCalledWith('http://localhost:7777/mainPoint?param=val1'); // passes
});
describe('mySecondMethod()', () => {
beforeEach(() => {
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
configurable: true,
value: {
href: 'http://localhost:7777/mainPoint?param=val2',
},
});
});
it('should call with val2', () => {
myMethod();
expect(myService.detectURLCall).toHaveBeenCalledWith('http://localhost:7777/mainPoint?param=val2'); // fails, the received call was ending with =val1
})
I know that the jest unit tests run in parallel, I tried doing clearMocks() in an afterEach() inside myMethod() test suite but it did not help and my second test suit still detects what we defined in the first one..
The following looks to be a good solution for your issue:
https://github.com/simon360/jest-environment-jsdom-global
(built on top of jest's built-in jest-environment-jsdom, and has a good readme that directly relates)
This ensures that you have one defined environment per file, which would definitely be the root issue as you said.

Why doesn't node recognize this imported function?

I define this function a file like this, and immediately export it.
const watchMongo = () => {
console.log("foo")
};
module.exports = { watchMongo };
Then I import it and run it the main app, as shown here.
const watchMongo = require('./controllers/path');
watchMongo();
However, I get this error when ran. "watchMongo is not a function".
When I console log 'watchMongo' instead of running it, I'm told "{ watchMongo: [Function: watchMongo] }"
So Node sees and recognizes the function? Until the function needs to be ran? What??
You can do one of 2 things. You are defining watchMongo as a named export. You can either do:
const { watchMongo } = require('./controllers/path');
or on your declaration file export like:
module.exports = watchMongo;

jest support for mocking javascript 'classes'

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

Categories