Why is code coverage inadequate for my controller? - javascript

I have below method in my controller. How can I test it? Annotation coverage is failing. Actually I am not able to define code for coverage in jest file.
What parameter I should add in my test file and pass to cover it?
#Post('upload')
#UseInterceptors(
FileInterceptor('image', {
storage: diskStorage({
destination: './uploads',
filename(_, file, callback) {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return callback(null, `${randomName}${extname(file.originalname)}`);
},
}),
})
)
uploadFile(#UploadedFile() file) {
return {
url: `http://localhost:2300/api/inventory/uploads/${file.filename}`,
originalFileName: `${file.originalname}`,
};
}
It is working perfectly. I wrote this jest test case for same.
it('Single file upload', async () => {
const response = await controller.uploadFile(mockFile);
const fileService = new InventoryController(service);
expect(response).toBeTruthy();
expect(response).toStrictEqual(
expect.objectContaining({
originalFileName: expect.any(String),
url: expect.any(String),
}),
);
});
This is successful but coverage is failing for this line.
#UseInterceptors(
FileInterceptor('image', {
storage: diskStorage({
destination: './uploads',
filename(_, file, callback) {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return callback(null, `${randomName}${extname(file.originalname)}`);
},
}),
})
)

What I would do here if code coverage is an absolute must on all lines with unit tests, is move the filename function to a separate file that exports the method so it can be tested directly.
During unit tests, enhancers aren't called, only created, so you won't get any coverage other than the initial "yeah, this decorator works" kind of coverage. For this inner callback, you'll need a separate function.
The other option, if code coverage isn't as important a metric, would be to test this in an e2e test. That way you know for certain this works as intended

Related

Jest custom testing API, how to correct code frame

I'm looking to simplify my project's testing API where I am aiming for something like this:
testThing((t) => {
t(33);
t(44);
t(42);
})
Now I don't know how to get Jest to show the correct code frames for failed expect's. This is my current stab at an implementation:
const testThing = (callback: any) => {
callback((n: any) => {
test(n.toString(), () => {
expect(n).toBe(42);
});
});
};
Which results in the testThing definition to be shown for every failed test case. Here's a replit if you want to see it in action: https://replit.com/#grgr/jest-frame-issue#thing.test.js

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()

How to run unit tests with Cypress.io?

I have been using Cypress.io to run end-to-end tests. Recently, I have been using it to run unit tests as well. However, I have some issues with some small helper functions that I have built with NodeJs.
I have a create file called utils.spec.js in the following path <my-project-name>/cypress/integration/unit/utils.spec.js & I have written the following tests:
File: utils.spec.js
// Path to utils.js which holds the regular helper javascript functions
import { getTicketBySummary } from '../../path/to/utils';
describe('Unit Tests for utils.js methods', () => {
/**
* Array of objects mocking Tickets Object response
*/
const mockedTickets = {
data: {
issues: [
{
id: 1,
key: 'ticket-key-1',
fields: {
summary: 'This is ticket number 1',
},
},
{
id: 2,
key: 'ticket-key-2',
fields: {
summary: 'This is ticket number 2',
},
},
],
},
};
const mockedEmptyTicketsArray = [];
it('returns an array containing a found ticket summary', () => {
expect(
getTicketBySummary({
issues: mockedTickets,
summaryTitle: 'This is ticket number 1',
})
).eq(mockedTickets.data.issues[0]);
});
it('returns an empty array, when no ticket summary was found', () => {
expect(
getTicketBySummary({
issues: mockedTickets,
summaryTitle: 'This is ticket number 3',
})
).eq(mockedEmptyTicketsArray);
});
});
File: utils.js
const fs = require('fs');
/**
* Method to search for an issue by its title
* Saving the search result into an array
*
* #param {array} - issues - an array containing all existing issues.
* #param {string} - summaryTitle - used to search an issue by title
*/
const getTicketBySummary = ({ issues, summaryTitle }) =>
issues.data.issues.filter(issueData => {
return issueData.fields.summary === summaryTitle ? issueData : null;
});
/**
* Method to read a file's content
* Returns another string representing the file's content
*
* #param {str} - file - a passed string representing the file's path
*/
const readFileContent = file => {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) return reject(err);
return resolve(data);
});
});
};
module.exports = { getTicketBySummary, readFileContent };
However, when I run the command: npx cypress run --spec=\"cypress/integration/unit/utils.spec.js\" --env mode=terminal, I get the error:Module not found: Error: Can't resolve 'fs'.
Also if I commented out the fs import & its function I get another error:
1) An uncaught error was detected outside of a test:
TypeError: The following error originated from your test code, not from Cypress.
> Cannot assign to read only property 'exports' of object '#<Object>'
When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure
I did some digging on the second error & it seems the describe method is defined. How can I fix both issues? What am I doing wrong?
You can use tasks to execute node code. In your plugins.js create the task with the arguments you need, returning the calculated value:
on('task', {
// you can define and require this elsewhere
getTicketBySummary('getTicketBySummary', { issues, summaryTitle }) {
return issues.data.issues.filter(...);
}
})
}
In your test, execute the task via cy.task:
it('returns an array containing a found ticket summary', () => {
cy.task('getTicketBySummary', {
issues: mockedTickets,
summaryTitle: 'This is ticket number 1',
}).then(result => {
expect(result).eq(mockedTickets.data.issues[0]);
})
});
That being said, getTicketBySummary looks like a pure function that doesn't depend on fs. Perhaps separate helper functions that actually need node as that could avoid need cy.task. If you want to be able to import commonjs (require) via ES6 import/export you would usually need to setup build tools (babel/rollup/etc) to be able to resolve that effectively.
Hopefully that helps!

#sentry/node integration to wrap bunyan log calls as breadcrumbs

Sentry by defaults has integration for console.log to make it part of breadcrumbs:
Link: Import name: Sentry.Integrations.Console
How can we make it to work for bunyan logger as well, like:
const koa = require('koa');
const app = new koa();
const bunyan = require('bunyan');
const log = bunyan.createLogger({
name: 'app',
..... other settings go here ....
});
const Sentry = require('#sentry/node');
Sentry.init({
dsn: MY_DSN_HERE,
integrations: integrations => {
// should anything be handled here & how?
return [...integrations];
},
release: 'xxxx-xx-xx'
});
app.on('error', (err) => {
Sentry.captureException(err);
});
// I am trying all to be part of sentry breadcrumbs
// but only console.log('foo'); is working
console.log('foo');
log.info('bar');
log.warn('baz');
log.debug('any');
log.error('many');
throw new Error('help!');
P.S. I have already tried bunyan-sentry-stream but no success with #sentry/node, it just pushes entries instead of treating them as breadcrumbs.
Bunyan supports custom streams, and those streams are just function calls. See https://github.com/trentm/node-bunyan#streams
Below is an example custom stream that simply writes to the console. It would be straight forward to use this example to instead write to the Sentry module, likely calling Sentry.addBreadcrumb({}) or similar function.
Please note though that the variable record in my example below is a JSON string, so you would likely want to parse it to get the log level, message, and other data out of it for submission to Sentry.
{
level: 'debug',
stream:
(function () {
return {
write: function(record) {
console.log('Hello: ' + record);
}
}
})()
}

jest.mock is not a function in react?

Could you please tell me how to test componentDidMount function using enzyme.I am fetching data from the server in componentDidMount which work perfectly.Now I want to test this function.
here is my code
https://codesandbox.io/s/oq7kwzrnj5
componentDidMount(){
axios
.get('https://*******/getlist')
.then(res => {
this.setState({
items : res.data.data
})
})
.catch(err => console.log(err))
}
I try like this
it("check ajax call", () => {
const componentDidMountSpy = jest.spyOn(List.prototype, 'componentDidMount');
const wrapper = shallow(<List />);
});
see updated code
https://codesandbox.io/s/oq7kwzrnj5
it("check ajax call", () => {
jest.mock('axios', () => {
const exampleArticles:any = {
data :{
data:['A','B','c']
}
}
return {
get: jest.fn(() => Promise.resolve(exampleArticles)),
};
});
expect(axios.get).toHaveBeenCalled();
});
error
You look like you're almost there. Just add the expect():
expect(componentDidMountSpy).toHaveBeenCalled();
If you need to check if it was called multiple times, you can use toHaveBeenCalledTimes(count).
Also, be sure to mockRestore() the mock at the end to make it unmocked for other tests.
List.prototype.componentDidMount.restore();
To mock axios (or any node_modules package), create a folder named __mocks__ in the same directory as node_modules, like:
--- project root
|-- node_modules
|-- __mocks__
Inside of there, make a file named <package_name>.js (so axios.js).
Inside of there, you'll create your mocked version.
If you just need to mock .get(), it can be as simple as:
export default { get: jest.fn() }
Then in your code, near the top (after imports), add:
import axios from 'axios';
jest.mock('axios');
In your test, add a call to axios.get.mockImplementation() to specify what it'll return:
axios.get.mockImplementation(() => Promise.resolve({ data: { data: [1, 2, 3] } });
This will then make axios.get() return whatever you gave it (in this case, a Promise that resolves to that object).
You can then do whatever tests you need to do.
Finally, end the test with:
axios.get.mockReset();
to reset it to it's default mocked implementation.

Categories