I am new to unit testing in JS, learning jest framework. I have problem with simple toThrow() matcher that I test my throwing error function with.
I have written simple foo() function that only throws an Error and test it with toThrow() matcher.
index.js
export const foo = () => {
throw new Error('bar');
};
index.test.js
import {foo} from './index';
test('foo', () => {
expect(foo()).toThrow();
});
As far as I understand, as the function throws error in any case, the expected result checked by toThrow() should resolve to a passed test. However, when I run yarn test I get the following failure:
FAIL index.test.js
✕ foo (3ms)
● foo
bar
21 |
22 | export const foo = () => {
> 23 | throw new Error('bar');
| ^
24 | };
25 |
at foo (index.js:23:9)
at Object.<anonymous> (index.test.js:13:10)
Test Suites: 1 failed, 1 total
Is there some error in my code or maybe my understanding of the toThrow() matcher?
You need to pass a function to expect when using toThrow.
In this case you can simply pass foo:
index.js
export const foo = () => {
throw new Error('bar');
};
index.test.js
import { foo } from './index';
test('foo', () => {
expect(foo).toThrow(); // Success!
});
If throwing depends on a parameter then you can pass an arrow function that calls your function:
index.js
export const foo = bar => {
if (bar > 0) throw new Error('bar > 0');
};
index.test.js
import { foo } from './index';
test('foo', () => {
expect(() => foo(0)).not.toThrow(); // Success!
expect(() => foo(1)).toThrow(); // Success!
});
Related
I have a soap RestAPI to call for excecuting a function from a service for which i have used soap lib with async await. The code is working fine. When comes to unit testing the test case fails at the callback method returning from the client. The code and UT error follows.
Function to call Soap Client - Code - Helper.ts
class soapHelper {
public async registerInSoap(
uploadRequest: ImportExperimentRequestDto,
): Promise<{ RegisterExperimentResult: IffManPublishResponseDto }> {
const url = "http://test.com/Services/Req.svc?wsdl";
const client = await soap.createClientAsync(flavourUrl);
return client.RegisterExperimentAsync({ upload: uploadRequest});
}
}
Test Case - Code
describe("**** Register via soap service ****", () => {
it("** should excecute register method **", async () => {
const request = cloneDeep(MOCK.API_PAYLOAD);
const clientResponse = {
RegisterExperimentAsync: jest.fn(),
};
jest.spyOn<any, any>(soap, "createClientAsync").mockReturnValueOnce(() => Promise.resolve(clientResponse));
const result= await soapHelper.registerInSoap(request);
expect(result).toEqual({ Result: AFB_MOCK_RESPONSE.API_RESPONSE });
});
});
Error
TypeError: client.RegisterExperimentAsync is not a function
enter image description here
The mock resolved value of soap.createClientAsync() should be a soap client.
E.g.
index.ts:
import * as soap from 'soap';
interface ImportExperimentRequestDto {}
interface IffManPublishResponseDto {}
export class SoapHelper {
public async registerInSoap(
uploadRequest: ImportExperimentRequestDto,
): Promise<{ RegisterExperimentResult: IffManPublishResponseDto }> {
const url = 'http://test.com/Services/Req.svc?wsdl';
const client = await soap.createClientAsync(url);
return client.RegisterExperimentAsync({ upload: uploadRequest });
}
}
index.test.ts:
import { Client } from 'soap';
import * as soap from 'soap';
import { SoapHelper } from '.';
describe('**** Register via soap service ****', () => {
it('** should excecute register method **', async () => {
const request = {};
const mClient = ({
RegisterExperimentAsync: jest.fn().mockResolvedValueOnce({ Result: 'mock result' }),
} as unknown) as Client;
jest.spyOn(soap, 'createClientAsync').mockResolvedValue(mClient);
const soapHelper = new SoapHelper();
const result = await soapHelper.registerInSoap(request);
expect(result).toEqual({Result: 'mock result'});
});
});
Test result:
PASS stackoverflow/72604110/index.test.ts (9.87 s)
**** Register via soap service ****
✓ ** should excecute register method ** (3 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.471 s
I'm using the image-map-resizer plugin for a responsive image mapping. And I need help in writing testcase for it.
This is my html :
<img src="...." width="100%" usemap="#usaMap" (load)="imageResized();">
This is my TS file :
declare function imageMapResize(): void;
#Component({...
})
...
imageResized() {
imageMapResize(); // Javascript function in imageMapResizer.min.js
}
and this is my test file :
it('should call imageMapResize() on imageResized method', () => {
spyOn(component, 'imageMapResize').and.callThrough();
component.imageResized();
expect(component.imageMapResize).toHaveBeenCalled()
});
And this is the error that I'm getting on compilation :
Argument of type '"imageMapResize"' is not assignable to parameter of type '"ngOnInit" | "currentMap" | "mapDataObj" | "imageResized"'.
-- spyOn(component, 'imageMapResize').and.callThrough();
I know it's pretty simple, but I recently started unit testing
The issue is that you can only spy on public methods and the error tells you that the only methods you can spy on are: '"ngOnInit" | "currentMap" | "mapDataObj" | "imageResized"'.
I see you are spying on imageMapResize and that's not a public method.
I would do this if I were you.
import { By } from '#angular/platform-browser';
....
it('should call imageResized on load', () => {
// make the CSS selector more specific if there are multiple images
const resizeSpy = spyOn(component, 'imageResized');
const img = fixture.debugElement.query(By.css('img'));
img.triggerEventHandler('load', {});
expect(resizeSpy).toHaveBeenCalled();
});
I'm practicing test-first development and I want to ensure that method in a class always calls my logger at the warn level with a message. My class is defined like so:
import { log4js } from '../config/log4js-config'
export const logger = log4js.getLogger('myClass')
class MyClass {
sum(numbers) {
const reducer = (accumulator, currentValue) => accumulator + currentValue
const retval = numbers.reduce(reducer))
if (retval < 0) {
logger.warn('The sum is less than zero!')
}
return retval
}
}
const myClass = new MyClass()
export { myClass }
My test looks like this:
import { myClass, logger } from './MyClass'
import { log4js } from '../config/log4js-config'
jest.mock('log4js')
describe('MyClass', () => {
it('logs a warn-level message if sum is negative', () => {
logger.warn = jest.fn()
logger._log = jest.fn()
myClass.sum([0, -1])
expect(logger.warn).toHaveBeenCalled() // <--- fails
expect(logger._log).toHaveBeenCalled() // <--- fails
})
})
I've also tried to mock log4js.Logger._log in the setup but that didn't seem to work either. 😕 Any suggestions are appreciated!
The thing with mocking is that you need to provide the mock, simplest method for me is through the mock factory. However i would recomend also some refactoring:
import { getLogger } from 'log4js'
export const logger = getLogger('myClass')
logger.level = 'debug'
// export the class itself to avoid memory leaks
export class MyClass {
// would consider even export just the sum function
sum(numbers) {
const reducer = (accumulator, currentValue) => accumulator + currentValue
const retval = numbers.reduce(reducer))
if (retval < 0) {
logger.warn('The sum is less than zero!')
}
return retval
}
}
import log4js from 'log4js';
import { MyClass } from "./class";
jest.mock('log4js', () => {
// using the mock factory we mimic the library.
// this mock function is outside the mockImplementation
// because we want to check the same mock in every test,
// not create a new one mock every log4js.getLogger()
const warn = jest.fn()
return {
getLogger: jest.fn().mockImplementation(() => ({
level: jest.fn(),
warn,
})),
}
})
beforeEach(() => {
// reset modules to avoid leaky scenarios
jest.resetModules()
})
// this is just some good habits, if we rename the module
describe(MyClass, () => {
it('logs a warn-level message if sum is negative', () => {
const myClass = new MyClass()
myClass.sum([0, -1])
// now we can check the mocks
expect(log4js.getLogger).toHaveBeenCalledTimes(1) // <--- passes
// check exactly the number of calls to be extra sure
expect(log4js.getLogger().warn).toHaveBeenCalledTimes(1) // <--- passes
})
})
Maybe simply spying on logger methods can do the trick
import { myClass, logger } from './MyClass'
describe('MyClass', () => {
it('logs a warn-level message if sum is negative', () => {
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {});
const _logSpy = jest.spyOn(logger, '_log').mockImplementation(() => {});
myClass.sum([0, -1])
expect(warnSpy).toHaveBeenCalled()
expect(_logSpy).toHaveBeenCalled()
})
})
We have a function like this:
export function* postPermitApplicationRequest() {
try {
const uris = yield select(state => getUris(state));
.... (more yields hereafter)
We test this function with Jest and Chai as follows:
...
const action = { type: Action.POST_PERMIT_APPLICATION };
const generator = postPermitApplicationRequest(action);
it('must select uris from state', () => {
const nextCall = generator.next().value;
const uris = select(state => getUris(state));
expect(nextCall).to.deep.equal(uris);
});
However the expect fails:
AssertionError: expected { Object (##redux-saga/IO, SELECT) } to deeply equal { Object (##redux-saga/IO, SELECT) }
at Assertion.assertEqual (node_modules/chai/lib/chai/core/assertions.js:485:19)
at Assertion.ctx.(anonymous function) [as equal] (node_modules/chai/lib/chai/utils/addMethod.js:41:25)
at Object.<anonymous> (src/app/pa/PermitApplicationServiceSagas.test.js:20:43)
at process._tickCallback (internal/process/next_tick.js:109:7)
The two objects both look like:
{ '##redux-saga/IO': true,
SELECT: { selector: [Function], args: [] } }
However the selector functions are different. The one that is the outcome of generator.next() contains code coverage skip hints:
function (state) {/* istanbul ignore next */cov_zrpq42gyn.f[12]++;cov_zrpq42gyn.s[19]++;return (/* istanbul ignore next */(0, _Selectors.getUris)(state));}
while the original function doesn't:
function (state) {return (0, _Selectors.getUris)(state);}
It looks like generator.next() adds these hints and the assertion fails
What do we wrong here?
We use redux-saga 0.14.8
The tests fail because in your saga and your test you create a new functions every time you execute the code. These both functions will be compared but are not the same instance.
You can simply use select(getUris) in your saga and your test because both will reference to the same function.
Your saga:
export function* postPermitApplicationRequest() {
try {
const uris = yield select(getUris);
.... (more yields hereafter)
Your test:
...
const action = { type: Action.POST_PERMIT_APPLICATION };
const generator = postPermitApplicationRequest(action);
it('must select uris from state', () => {
const nextCall = generator.next().value;
const uris = select(getUris);
expect(nextCall).to.deep.equal(uris);
});
I want to unit-test the following simplified module:
const Logger = require('logplease');
const logger = Logger.create('utils');
const tester = {
one: () => {
logger.log('called real one()');
tester.two();
},
two: () => {
logger.log('called real two()');
},
};
module.exports = {
one: tester.one,
two: tester.two
};
I'm replacing the external dependency logplease using Proxyquire, which works very well. However I need to stub two() because I want to unit-test one() while eliminating the side-effects two() produces when it runs in real code.
it.only('stubbing functions on the "proxyquired" object under test', function(done) {
const loggerStub = {
create: () => {
return { log: (msg) => { console.log('fake logger: ', msg); } };
}
};
let tester = proxyquire('../tester', { 'logplease': loggerStub });
let stub2 = sinon.stub(
tester,
'two',
() => {
console.log('called fake stub of two()');
}
);
tester.one();
console.log('call count 2: ', stub2.callCount);
done();
});
Output I get:
fake logger: called real one()
fake logger: called real two()
call count 2: 0
Output I expect:
fake logger: called real one()
called fake stub of two()
call count 2: 1
Why doesn't my stub function run?
Short answer:
const Logger = require('logplease');
const logger = Logger.create('utils');
const tester = {
one: () => {
logger.log('called real one()');
tester.two();
},
two: () => {
logger.log('called real two()');
},
};
module.exports = tester;
Explanation: scope
You exported one and two as:
module.exports = {
one: tester.one,
two: tester.two
};
In this case tester.one knows only about this function:
two: () => {
logger.log('called real two()');
}
and have no idea about stubbed two. So you have two versions of two, just try to invoke tester.two() inside test.