i try to use the testing tool mocha in node. Consider the following test scenario
var requirejs = require('requirejs');
requirejs.config({
//Pass the top-level main.js/index.js require
//function to requirejs so that node modules
//are loaded relative to the top-level JS file.
nodeRequire: require
});
describe('Testing controller', function () {
it('Should be pass', function (done) {
(4).should.equal(4);
done();
});
it('Should avoid name king', function (done) {
requirejs(['../server/libs/validate_name'], function (validateName) {
var err_test, accountExists_test, notAllow_test, available_test;
validateName('anu', function (err, accountExists, notAllow, available) {
accountExists.should.not.be.true;
done();
});
});
});
});
as a testing result i have got:
$ make test
./node_modules/.bin/mocha \
--reporter list
. Testing controller Should be pass: 0ms
1) Testing controller Should avoid name anu
1 passing (560 ms)
1 failing
1) Testing controller Should avoid name anu:
Uncaught TypeError: Cannot read property 'should' of null
at d:\townspeech\test\test.spec.js:23:30
at d:\townspeech\server\libs\validate_name.js:31:20
at d:\townspeech\test\test.spec.js:22:13
at Object.context.execCb (d:\townspeech\node_modules\requirejs\bin\r.js:1869:33)
at Object.Module.check (d:\townspeech\node_modules\requirejs\bin\r.js:1105:51)
at Object.Module.enable (d:\townspeech\node_modules\requirejs\bin\r.js:1376:22)
at Object.Module.init (d:\townspeech\node_modules\requirejs\bin\r.js:1013:26)
at null._onTimeout (d:\townspeech\node_modules\requirejs\bin\r.js:1646:36)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
make: *** [test] Error 1
The first pass without any complication but the second, it seems like, that could not attach module shouldjs. Why?
I had the same problem. I solved it by using:
(err === null).should.be.true;
You can use should directly
should.not.exist(err);
That's a known problem with the should library: As it extends object, of course it only works when you have a concrete object. As null, by definition, means that you don't have an object, you can not call any methods on it or access any of its properties.
Hence, should is not available in this case.
Basically, you have two options of how to deal with this:
You could exchange actual and expected. This way, as long as you do not expect null, you have an object in the beginning and hence can access its should property. But, this is not nice, as it changes semantics, and does not work in all cases.
You could exchange the should library by another assertion library that does not have this problem.
Personally, I'd go with option 2, and my highly subjective personal favorite for this is node-assertthat (which has been written by me, hence it's my favorite).
Anyway, there are lots of other options, such as expect. Feel free to use any of these assertion libraries that best suits your style of writing tests.
One more option that I don't see mentioned here:
should(err === null).be.null
In you example that would would be:
should(accountExists).be.null
I always find expect works better with null and undefined as follows:
expect(err).be.null
Related
I'm trying to mock the #hmscore/react-native-hms-location with jest. The function that one of my providers uses is HMSLocation.FusedLocation.Native.hasPermission(), but in one of my test I always get the following:
TypeError: Cannot read property 'Native' of undefined at call (C:\laragon\www\qmy-project\providers\LocationProvider.js:65:57)
I've tried to mock all the Library like this:
jest.mock('#hmscore/react-native-hms-location');
Even like this another way:
jest.mock('#hmscore/react-native-hms-location', () => {
const original = jest.requireActual('#hmscore/react-native-hms-location');
return original;
});
Always is the same. Any help please?
I would like to test a small React web app where I use the global fetch method.
I tried to mock fetch in this way:
global.fetch = jest.spyOn(global, 'fetch').mockImplementation(endpoint =>
Promise.resolve({
json: () => Promise.resolve(mockResponse)
})
);
... but the mock seems to be ignored, while the built-in fetch seems to be used: Error: connect ECONNREFUSED 127.0.0.1:80 ... looks like a failed call to the built-in fetch.
I then tried to use jest.fn instead of jest.spyOn:
global.fetch = jest.fn(endpoint =>
Promise.resolve({
json: () => Promise.resolve(mockResponse)
})
);
... and was surprised to see a different error. Now the mock seems to be taken into consideration, but at the same time is not working correctly:
TypeError: Cannot read property 'then' of undefined
8 | this.updateTypes = this.props.updateTypes;
9 | this.updateTimeline = this.props.updateTimeline;
> 10 | fetch('/timeline/tags')
| ^
11 | .then(res => res.json())
12 | .then(tags => tags.map(tag => <option value={tag} key={tag} />))
13 | .then(options => options.sort((a, b) => a.key.localeCompare(b.key)))
I find the documentation of Jest and React Testing Library a bit confusing, honestly. What might be the problem with what I am doing?
Edit
The React component I am trying to test is called "App", was generated with Create React App, and was changed to include a call to fetch. I can gladly provide the code for this component, but I believe that the problem lies in the tests.
At the beginning of my App.test.js file, I import React from 'react';, then import { render, fireEvent, waitFor, screen } from '#testing-library/react';, and finally import App from './App';. I subsequently attempt to mock fetch in one of the ways I described, and then declare the following test:
test('renders a list of items, upon request', async () => {
const app = render(<App />);
fireEvent.click(screen.getByText('Update'));
await waitFor(() => screen.getByRole('list'));
expect(screen.getByRole('list')).toBeInTheDocument();
expect(screen.getByRole('list')).toHaveClass('Timeline');
});
Finally, I end my test file with global.fetch.mockRestore();.
That there's ECONNREFUSED error instead of fetch is not defined means that fetch has been polyfilled. It's not a part of JSDOM and isn't polyfilled by Jest itself but is specific to current setup. In this case the polyfill is provided by create-react-app.
It's always preferable to mock existing global function with jest.spyOn and not by assigning them as global properties, this allows Jest to do a cleanup. A thing like global.fetch = jest.spyOn(global, 'fetch') should never be done because this prevents fetch from being restored. This can explain TypeError: Cannot read property 'then' of undefined error for seemingly correctly mocked function.
A correct and safe way to mock globals is to mock them before each test and restore after each test:
beforeEach(() => {
jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest.fn().mockResolvedValue(mockResponse)
})
});
afterEach(() => {
jest.restoreAllMocks();
});
There should be no other modifications to global.fetch in order for a mock to work correctly.
A preferable way to restore mocks and spies is to use configuration option instead of jest.restoreAllMocks because not doing this may result in accidental test cross-contamination which is never desirable.
Another reason for TypeError: Cannot read property 'then' of undefined error to appear is that Jest incorrectly points at fetch line, and the error actually refers to another line. This can happen if source maps don't work correctly. If fetch is mocked correctly and there are other then in the same component, it's a plausible explanation for the error.
I've written a node module with UMD (Universal Module Definition) Pattern to make it compatible with amd and plain javascript as well.
The definition is as below
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.myModule = factory();
}
}(this, function (global) {
...
return {};
}));
With mocha & chai the tests are running fine, the only issue is that since the tests are executed from within node the code coverage for define(factory); and root.myModule = factory(); lines is showing red.
Just wanted to know if there is any way(maybe a hack) to get 100% coverage for this code with mocha chai.
Sure!
How coverage tools for JS basically work, is that their injecting little bit of tracker code for each line. So what you need is that the execution flow wonders there.
Since this is an if-else logic, to get 100% you will be needing multiple test scenarios to cover this.
Note: it's also important for actually achieving this effect, that loading that file (factory) should happen after every scenario setup, because that is when those module-loading-if-else lines are executed. Now this depends on what is your file/module loading in your test execution environment.
Now to enter into that branch of the if-else create a test scenario where you can just enforce the existence of that define function with a few lines like (e.g. in a beforeEach section):
define = function () {
console.log('hello I am a fake define, but I will be defined ...');
console.log(' ... so that if else branch will be executed.');
}
// and of course the needed .amd property.
define.amd = true;
I think the same weird stuff can be done w/ the exports variable, but it might be hard to achieve if you are just pure CommonJS require-ing that file, since require-ing defines the exports variable for that file context, so maybe you could modify your production code for this weird case. I wouldn't do that, but you said any way:
...
} else if (typeof exports === 'object' && testExportsEnabled) {
...
Now using this you can just have one scenario where in beforeEach you do: testExportsEnabled = true; and w/ this being false, the last branch should be executed.
Of course you can execute all branches with such a simple trick! Now to have no code modification AND trigger the third branch (basically the case for simple script-tag-loading in browser), you will need to dig in into your module loading code and create some hacks there, but that is a bit too much for me in an SO question! :]
Take care!
I am building some tests with mocha and chai(expect).
Keeping it simple as I am learning about testing methodology as I go along.
I have a mysql db layer in a config file.
Testing the db parameters, I ran into a weird issue.
These db parameters test fine:
host= 'localhost',
user='foo',
password='bar',
The tests:
var expect = require('chai').expect;
var db = require('../db/config.ini');
describe('Database Access', function() {
it('HOST parameter should be a string', function() {
expect(host).to.be.a('string');
});
it('USER parameter should be a string', function() {
expect(user).to.be.a('string');
});
it('PASSWORD parameter should be a string', function() {
expect(password).to.be.a('string');
});
it('DB parameter should be a string', function() {
expect(db).to.be.a('string');
});
it('HOST parameter should equal localhost', function() {
expect(host).to.equal('localhost');
});
it('USER parameter should equal foo', function() {
expect(user).to.equal('foo');
});
it('PASSWORD parameter should equal bar', function() {
expect(password).to.equal('bar');
});
it('DB parameter should equal thatone', function() {
expect(context).to.equal('thatone');
});
});
When I add the database to choose,
db='thatone';
The test fails the parameter because it reads it as an object.
1) Database Access DB parameter should be a string:
AssertionError: expected {} to be a string
at Context.<anonymous> (test/db_tests.js:21:20)
If I change the variable name to "context" the test passes as expected.
I'm wondering if there is something obvious I am missing about using "db" as a variable.
UPDATE
Really stupid, novice level mistake.
So focused on learning testing methodology I didn't realize I had created the
'db' var as a require to the 'ini' and then referenced it later as though it was unique.
Really dumb. Rushing through this recklessly to get to a destination, and failing to follow some good methodology.
The result of executing this is not a string:
var db = require('../db/config.ini');
It seems you are trying to get a file in some INI dialect to be meaningfully interpreted by Node. Node does not support this by default. If you do not get an error while loading it, the most likely reason is that the text you have in there happens to also be valid JavaScript but since INI files do not contain proper code to export something (i.e. the file does not contain exports.db = "something" or module.exports = { ... } or something similar), then the module has the value {}.
You need to add one of the multiple npm packages that will automatically interpret an INI file and provide a meaningful value. I cannot recommend one as I don't use INI files in my software but you can search npm for a package that will perform the translation for you.
I have a module which is dependent upon both, PhotoSwipe and PhotoSwipeUI_Default. I am requiring both modules, like so and inside of my module I am initialising my gallery. EG:
define("modules/lightboxStart", ["vendor/PhotoSwipe", "vendor/PhotoSwipeUI_Default"], function (PhotoSwipe, PhotoSwipeUI_Default) {
var StartGallery = function(){
var lightBox = new PhotoSwipe($pswp, PhotoSwipeUI_Default, items, options);
lightBox.init();
}
StartGallery()
}
The gallery then works as expected but require is throwing a mismatched anonymous define module error in PhotoSwipe.js.
After looking through the docs I came to the conclusion that I need to run both scripts(PhotoSwipe and PhotoSwipeUI_Default) through the require.js API.
I then defined each, like so:
define('PhotoSwipe', function() {
//PhotoSwipe script here
return PhotoSwipe
})
And
define('PhotoSwipeUI_Default', function() {
//PhotoSwipeUI_Default script here
return PhotoSwipeUI_Default
})
This then eliminates the mismatched anonymous define module error I was getting earlier, but I am getting this error now instead:
PhotoSwipe is not a function
How could I fix this? I am essentially trying to access the PhotoSwipe function within PhotoSwipe.js and pass through my arguments but I am unable to do so without generating a load of js errors on the page and I can not figure out why.
Help much appreciated!
define( ["modules/lightboxStart", "vendor/PhotoSwipe", "vendor/PhotoSwipeUI_Default"], function (lightboxStart, PhotoSwipe, PhotoSwipeUI_Default) { //i think so}