I am using Jest to test my API and when I run my tests, my JSON file results.json gets written to due to the following line in my API app.js (which I don't want happening):
fs.writeFile('results.json', JSON.stringify(json), (err, result) => {
if (err) console.log('error', err);
});
This is what my Jest file looks like:
const request = require('supertest');
const app = require('./app');
// Nico Tejera at https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object
function serialise(obj){
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
}
describe('Test /addtask', () => {
test('POST /addtask Successfully redirects if newDate and newTask filled in correctly', () => {
const params = {
newTask: 'Example',
newDate: '2020-03-11'
};
return request(app)
.post('/addtask')
.send(serialise(params))
.expect(301);
});
});
I tried creating a mock of the JSON file and placed it outside the describe statement to prevent the actual results.json file being written to:
jest.mock('./results.json', () => ({ name: 'preset1', JSONtask: [], JSONcomplete: [] }, { name: 'preset2', JSONtask: [], JSONcomplete: [] }));
But this doesn't change anything. Does anyone have any suggestions?
I have seen other solutions to similar problems but they don't provide the answer I'm looking for.
EDIT: Although not a very good method, one solution to my problem is to wrap the fs.writeFile within the statement
if (process.env.NODE_ENV !== 'test') {
//code
};
although this would mean that fs.writeFile cannot be tested upon.
NOTE: I am still accepting answers!
Your issue is that the code you want to test has a hard-coded I/O operation in it, which always makes things harder to test.
What you'll want to do is to isolate the dependency on fs.writeFile, for example into something like a ResultsWriter. That dependency can then be injected and mocked for your test purposes.
I wrote an extensive example on a very similar case with NestJS yesterday under how to unit test a class extending an abstract class reading environment variables, which you can hopefully adapt to your needs.
jest.mock(path, factory) is for mocking JS modules, not file content.
You should instead mock fs.writeFile and check that it has been called with the expected arguments. The docs explain how to do it.
Related
Does loading multiple json files in single test suit is a good practice in cypress?
Something like this:
before(() => {
cy.fixture('productCatalogData').then((datajson) => {
recipeData = datajson.recipes;
return recipeData;
});
cy.fixture('loginData').then((datajson) => {
loginData = datajson;
return loginData;
});
});
If you need both the json files in one suite, I don't think there should be a problem using fixtures multiple times. However you can shorten the cy.fixture() code a bit like this:
before(() => {
cy.fixture('productCatalogData').as('productCatalogData')
cy.fixture('loginData').as('loginData')
})
it('Access fixtures data', function () {
// The test has to use "function" callback to make sure "this" points at the Mocha context
//Access fixtures data using this.productCatalogData and this.loginData
})
But one thing to add here, cypress removes aliases after every tests. So if you have just one test inside the suite then before() will work fine, but if you have multiple tests you have to use beforeEach().
let's imagine we have a promise that does a large amounts of operations and return helper functions.
A banal example:
const testPromise = testFn => () => {
const helper = Promise.resolve({testHelper: () => 'an helper function'}) // I/O Promise that returns an helper for testing
return helper.then(testFn).finally(() => console.log('tear down'));
}
// This describe would work as expected
describe('Async test approach', () => {
it('A test', testPromise(async ({testHelper}) => {
expect(testHelper()).toBe('an helper function')
}))
})
// This part doesn't work
describe('Async describe approach', testPromise(async ({testHelper}) => {
it('Test 1', () => {
expect(testHelper()).toBe('an helper function')
})
it('Test 2', () => {
expect(testHelper()).not.toBe('A chair')
})
}))
}
What I would like to achieve is something like the second example where I can use async code within describe without re-evaluating testPromise.
describe doesn't handle async so I am not even able to loop and create dynamic tests properly.
I did read many comments around saying that describe should only be a simple way to group tests but... then... how can someone make async generated tests based on I/O result?
Thanks
= ADDITIONAL CONSIDERATION =
Regarding all the comment you guys kindly added, I should have added few additional details...
I am well aware that tests must be defined synchronously :), that is exactly where problems starts. I totally disagree with that and I am trying to find an alternative that avoids before/after and doing it without specifying an external variable. Within Jest issues there was an open one to address that, it seems they did agree on making describe async but they won't do it. The reason is... Jest is using Jasmine implementation of describe and this "fix" should be done in there.
I wanted to avoid beforeAll and afterAll, as much as I could. My purpose was creating an easy (and neat) way to define integration tests tailored on my needs without letting users to worry about initialize and tear down stuff around. I will continue to use the Example 1 above style, that seems the best solution to me, even if it would be clearly a longer process.
Take a look at Defining Tests. The doc says:
Tests must be defined synchronously for Jest to be able to collect your tests.
This is the principle for defining test cases. Which means the it function should be defined synchronously. That's why your second example doesn't work.
Some I/O operations should be done in beforeAll, afterAll, beforeEach, afterEach methods to prepare your test doubles and fixtures. The test should be isolated from the external environment as much as possible.
If you must do this, maybe you can write the dynamically obtained testHelper function to a static js file, and then test it in a synchronous way
As it was noted, describe serves to group tests.
This can be achieved with beforeAll. Since beforeAll should be called any way, it can be moved to testPromise:
const prepareHelpers = (testFn) => {
beforeAll(() => {
...
return helper.then(testFn);
})
}
describe('Async describe approach', () => {
let testHelper;
prepareHelpers(helpers => { testHelper = helpers.testHelper });
...
I've been struggling over the past couple of weeks with unit testing a file upload react component with jest. Specifically, I'm trying to test whether or not the method onReadAsDataUrl is being called from FileReader in one of my methods. This is an example method I am testing:
loadFinalImage = async (file) => {
const reader = new FileReader();
reader.onloadend = () => {
this.setState({
imagePreviewUrl: reader.result,
validCard: true,
});
};
await reader.readAsDataURL(file);
}
This is how I am attempting to mock FileReader and test whether or not onReadAsDataUrl has been called:
it('is a valid image and reader.onReadAsDataUrl was called', () => {
const file = new Blob(['a'.repeat(1)], { type: 'image/png' });
wrapper = shallow(<ImageUpload />).dive();
const wrapperInstance = wrapper.instance();
const mockReader = jest.fn();
jest.spyOn('FileReader', () => jest.fn());
FileReader.mockImplementation(() => { return mockReader });
const onReadAsDataUrl = jest.spyOn(mockReader, 'readAsDataURL');
wrapperInstance.loadFinalImage(file);
expect(onReadAsDataUrl).toHaveBeenCalled();
});
After I run: yarn jest, I get the following test failure:
Cannot spyOn on a primitive value; string given.
I assume I am getting this error because I am not importing FileReader, but I am not exactly sure how I would import it or mock it because FileReader is an interface. Here is an image of the test failure:
I am a bit of a noob with jest, reactjs, and web development, but would love to learn how to conquer this problem. Some resources I have looked at so far are: Unresolved Shopify Mock of FileReader, How to mock a new function in jest, and Mocking FileReader with jasmine.
Any help would be greatly appreciated! Thank you in advance.
I personally could not get any of the jest.spyOn() approaches to work.
Using jest.spyOn(FileReader.prototype, 'readAsDataURL') kept generating a Cannot spy the readAsDataURL property because it is not a function; undefined given instead error,
and jest.spyOn(global, "FileReader").mockImplementation(...) returned a Cannot spy the FileReader property because it is not a function; undefined given instead error
I managed to successfully mock the FileReader prototype using the following:
Object.defineProperty(global, 'FileReader', {
writable: true,
value: jest.fn().mockImplementation(() => ({
readAsDataURL: jest.fn(),
onLoad: jest.fn()
})),
})
Then in my test, I was able to test the file input onChange method (which was making use of the FileReader) by mocking the event and triggering it manually like this:
const file = {
size: 1000,
type: "audio/mp3",
name: "my-file.mp3"
}
const event = {
target: {
files: [file]
}
}
wrapper.vm.onChange(event)
I hope it can help anyone else looking into this.
Quite possibly the OP has found an answer by now, but since I was facing pretty much the same problem, here's how I did it - taking input from another SO answer.
I think #Jackyef comment is the right way to go, but I don't think the call to mockImplementation you propose is correct.
In my case, the following turned out to be correct.
const readAsDataURL = jest
.spyOn(global, "FileReader")
.mockImplementation(function() {
this.readAsDataURL = jest.fn();
});
Worth noting that VSCode highlights a potential refactoring at the anonymous function. It suggests:
class (Anonymous function)
(local function)(): void
This constructor function may be converted to a class declaration.ts(80002)
I'm still relatively new to JS, so I'm afraid I can't explain what this is about, nor what refactoring should be done.
Allow me to note that a similar question to this one can be found here, but the accepted answer's solution did not work for me. There was another question along the same lines, the answer of which suggested to directly manipulate the function's prototypes, but that was equally non-fruitful.
I am attempting to use Jest to mock this NPM Module, called "sharp". It takes an image buffer and performs image processing/manipulation operations upon it.
The actual implementation of the module in my codebase is as follows:
const sharp = require('sharp');
module.exports = class ImageProcessingAdapter {
async processImageWithDefaultConfiguration(buffer, size, options) {
return await sharp(buffer)
.resize(size)
.jpeg(options)
.toBuffer();
}
}
You can see that the module uses a chained function API, meaning the mock has to have each function return this.
The Unit Test itself can be found here:
jest.mock('sharp');
const sharp = require('sharp');
const ImageProcessingAdapter = require('./../../adapters/sharp/ImageProcessingAdapter');
test('Should call module functions with correct arguments', async () => {
// Mock values
const buffer = Buffer.from('a buffer');
const size = { width: 10, height: 10 };
const options = 'options';
// SUT
await new ImageProcessingAdapter().processImageWithDefaultConfiguration(buffer, size, options);
// Assertions
expect(sharp).toHaveBeenCalledWith(buffer);
expect(sharp().resize).toHaveBeenCalledWith(size);
expect(sharp().jpeg).toHaveBeenCalledWith(options);
});
Below are my attempts at mocking:
Attempt One
// __mocks__/sharp.js
module.exports = jest.genMockFromModule('sharp');
Result
Error: Maximum Call Stack Size Exceeded
Attempt Two
// __mocks__/sharp.js
module.exports = jest.fn().mockImplementation(() => ({
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer:jest.fn().mockReturnThis()
}));
Result
Expected mock function to have been called with:
[{"height": 10, "width": 10}]
But it was not called.
Question
I would appreciate any aid in figuring out how to properly mock this third-party module such that I can make assertions about the way in which the mock is called.
I have tried using sinon and proxyquire, and they don't seem to get the job done either.
Reproduction
An isolated reproduction of this issue can be found here.
Thanks.
Your second attempt is really close.
The only issue with it is that every time sharp gets called a new mocked object is returned with new resize, jpeg, and toBuffer mock functions...
...which means that when you test resize like this:
expect(sharp().resize).toHaveBeenCalledWith(size);
...you are actually testing a brand new resize mock function which hasn't been called.
To fix it, just make sure sharp always returns the same mocked object:
__mocks__/sharp.js
const result = {
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer: jest.fn().mockReturnThis()
}
module.exports = jest.fn(() => result);
I am using Istanbul for code coverage, but i m getting very low coverage percentage particularly in Models file.
Consider the following is the model file:
ModelA.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var app = require('../server')
var db = require('../db/dbConnection');
var config = require('../configs/config')
const Schema1 = new Schema({ 'configurations': [] });
exports.save = function (aa, data, callback) {
var logMeta = {
file: 'models/modelA',
function: 'save',
data: {},
error: {}
}
if (!aa) {
return callback('aa is required')
}
global.logs[aa].log('info', 'AA: ' + aa, logMeta);
db.connectDatabase(aa, function(error, mongoDB){
if(error){
logMeta.data['error'] = error
global.logs[aa].log('error', 'error', logMeta);
return callback(error)
}
const ModelA = mongoDB.model('bbb', cccc);
ModelA.findOneAndUpdate({}, data, {upsert: true, new: true, runValidators: true}, function(error ,result){
if (error) {
logMeta.data['error'] = error
global.logs[aa].log('error', 'error', logMeta);
}
else {
logMeta.data = {}
logMeta.data['result'] = JSON.parse(JSON.stringify(result))
global.logs[aa].log('info', 'result', logMeta);
}
callback(error, result);
});
})
}
TestA.js:
var should = require('should'),
sinon = require('sinon'),
ModelA= require("../models/ModelA");
describe('Model test', function () {
it('Should save Model', function (done) {
var todoMock = sinon.mock(new ModelA({'configurations': []}));
var todo = todoMock.object;
todoMock
.expects('save')
.yields(null, 'SAVED');
todo.save(function(err, result) {
todoMock.verify();
todoMock.restore();
should.equal('SAVED', result, "Test fails due to unexpected result")
done();
});
});
});
But i am getting codecoverage percentage 20. SO how can i increase the percentage:
ALso:
1.Whether i have to mock the db.connectDatabase if yews how can i acheive that?
Whether i have to use TestDb to run all my UnitTest? Or i have to assert??
Code Coverage will work for Unit Test or integration test???
Please share your ideas. Thanks
I have been using Istanbul to 100% code cover most of my client/server projects so I might have the answers you are looking for.
How does it work
Whenever you require some local file, this gets wrapped all over the place to understand if every of its parts is reached by your code.
Not only the required file is tainted, your running test is too.
However, while it's easy to code cover the running test file, mocked classes and their code might never be executed.
todoMock.expects('save')
Accordingly to Sinon documentation:
Overrides todo. save with a mock function and returns it.
If Istanbul tainted the real save method, anything within that scope won't ever be reached so that you are actually testing that mock works, not that your real code does.
This should answer your question: Code Coverage will work for Unit Test or integration test ???
The answer is that it covers the code, which is the only thing you're interested from a code cover perspective. Covering Sinon JS is nobody goal.
No need to assert ... but
Once you've understood how Istanbul works, it follows up naturally to understand that it doesn't matter if you assert or not, all it matters is that you reach the code for real and execute it.
Asserting is just your guard against failures, not a mechanism interesting per se in any Istanbul test. When your assertion fails, your test does too, so it's good for you to know that things didn't work and there's no need to keep testing the rest of the code (early failure, faster fixes).
Whether you have to mock the db.connectDatabase
Yes, at least for the code you posted. You can assign db as generic object mock to the global context and expect methods to be called but also you can simplify your life writing this:
function createDB(err1, err2) {
return {
connectDatabase(aa, callback) {
callback(err1, {
model(name, value) {
return {
findOneAndUpdate($0, $1, $3, fn) {
fn(err2, {any: 'object'});
}
};
}
});
}
};
}
global.db = createDB(null, null);
This code in your test file can be used to create a global db that behaves differently accordingly with the amount of errors you pass along, giving you the ability to run the same test file various times with different expectations.
How to run the same test more than once
Once your test is completed, delete require.cache[require.resolve('../test/file')] and then require('../test/file') again.
Do this as many times as you need.
When there are conditional features detection
I usually run the test various times deleting global constructors in case these are patched with a fallback. I also usually store them to be able to put 'em back later on.
When the code is obvious but shouldn't be reached
In case you have if (err) process.exit(1); you rarely want to reach that part of the code. There are various comments understood by Istanbul that would help you skip parts of the test like /* istanbul ignore if */ or ignore else, or even the generic ignore next.
Please consider thinking twice if it's just you being lazy, or that part can really, safely, be skipped ... I got bitten a couple of times with a badly handled error, which is a disaster since when it happens is when you need the most your code to keep running and/or giving you all the info you need.
What is being covered?
Maybe you know this already but the coverage/lcov-report/index.html file, that you can open right away with any browser, will show you all the parts that aren't covered by your tests.