Mocha and Selenium WebDriver error thrown while assertion - node.js

I developing a web program with TDD methodology. Therefore, tests are using Selenium WebDriver library. But I wonder my program doesn't work correctly. Following is source codes.
public/index.html:
<h1 class="hello">Hello, world!</h1>
test/test.js:
'use strict'
var assert = require('assert')
var webdriver = require('selenium-webdriver'),
By = webdriver.By,
until = webdriver.until
var driver;
before(() => {
driver = new webdriver.Builder()
.forBrowser('chrome')
.build()
})
describe('Index page', () => {
before(() => {
driver.get('http://localhost:8080/')
})
it('should show hello greetings', () => {
let hello = driver.findElement(By.css('h1.hello'))
assert.equal(hello.getText(), 'Hello, world!')
})
})
package.json:
{
"name": "foobar",
"version": "1.0.0",
"description": "",
"main": "index.js",
"devDependencies": {
"mocha": "^2.5.3"
"selenium-webdriver": "^2.53.2"
},
"scripts": {
"start": "http-server public",
"test": "mocha"
},
"author": "",
"license": "ISC"
}
And I've run following command.
C:\Projects\foobar>npm install
So, when I run all tests with npm test command, it always failed regardless to npm start command was run or not.
C:\Projects\foobar>npm test
> foobar#1.0.0 test C:\Projects\foobar
> mocha
Index page
1) should show hello greetings
0 passing (62ms)
1 failing
1) Index page should show hello greetings:
AssertionError: ManagedPromise {
flow_:
ControlFlow {
propagateUnhandledRejections_: true,
activeQueue_:
TaskQueue {
== 'Hello, world!'
at Context.<anonymous> (C:\Projects\foobar\test\test.js:22:16)
at callFn (C:\Projects\foobar\node_modules\mocha\lib\runnable.js:326:21)
at Test.Runnable.run (C:\Projects\foobar\node_modules\mocha\lib\runnable.js:319:7)
at Runner.runTest (C:\Projects\foobar\node_modules\mocha\lib\runner.js:422:10)
at C:\Projects\foobar\node_modules\mocha\lib\runner.js:528:12
at next (C:\Projects\foobar\node_modules\mocha\lib\runner.js:342:14)
at C:\Projects\foobar\node_modules\mocha\lib\runner.js:352:7
at next (C:\Projects\foobar\node_modules\mocha\lib\runner.js:284:14)
at Immediate._onImmediate(C:\Projects\foobar\node_modules\mocha\lib\runner.js:320:5)
npm ERR! Test failed. See above for more details.
I think the main issue is in hello.getText() snippet. The getText() method couldn't get text of hello element.

Most driver operations are asynchronous and return a promise, not the actual return value of that operation. In your case you should change findElement but also getText to work in that manner. Also, you should set a timeout since the default one is set to 2000 ms and most pages won't have finished loading by then. Try this:
'use strict'
var assert = require('assert');
var webdriver = require('selenium-webdriver');
var By = webdriver.By;
var until = webdriver.until;
var driver;
before(function () {
driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
});
describe('Index page', function () {
this.timeout(5000);
before(function () {
driver.get('http://localhost:8080/');
});
it('should show hello greetings', function (done) {
driver.findElement(By.css('h1.hello'))
.then(elem => elem.getText() )
.then(text => assert.equal(text, 'Hello, world!') )
.then(done);
});
});
Note that arrow function don't define their own this object. You have to use regular callback functions to access this.timeout. Their use is, in any case, discouraged in mocha, see following:
Passing arrow functions to Mocha is discouraged. Their lexical binding of the this value makes them unable to access the Mocha context, and statements like this.timeout(1000); will not work inside an arrow function.

Related

Can't mock a ES6 imported function in NodeJS

I've been trying for some time to mock the fetchLiveMatches imported function with no success. I've been browsing for some ideas but I think I ran out of it, so I could use some help. Any ideas of what I am doing wrong?
live.test.js
import * as liveController from "./live";
import { jest } from "#jest/globals";
import * as liveService from "../service/live";
import { buildReq, buildRes, buildNext } from "../utils/testingHelper";
jest.mock("../service/live");
beforeEach(() => {
jest.clearAllMocks();
});
describe("Live Controller", () => {
test("calls fetchLiveMatches function to fetch from external API", async () => {
const req = buildReq();
const res = buildRes();
const next = buildNext();
await liveController.getLiveMatches(req, res, next);
expect(next).not.toHaveBeenCalled();
expect(liveService.fetchLiveMatches).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.status).toHaveBeenCalledTimes(1);
});
});
service/live.js
import axios from "axios";
async function fetchLiveMatches() {
// Some hidden code
return axios({
method: "get",
url: `${API_FOOTBALL_BASE_URL}${GET_EVENTS}${MATCH_LIVE}${WIDGET_KEY}${TIMEZONE}${DETAILS}`,
headers: {}
}).then(res => res.data);
}
export { fetchLiveMatches };
jest.config.js
export default {
testEnvironment: "jest-environment-node",
transform: {}
};
package.json
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"axios": "^1.1.3",
"eslint": "^8.26.0",
"jest": "^29.2.2",
"prettier": "^2.7.1"
},
"scripts": {
"start": "node --watch index.js",
"start:no-watch": "node index.js",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
}
}
Test Error Output
Live Controller › calls fetchLiveMatches function to fetch from external API
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function fetchLiveMatches]
Just posting the solution I found for anyone who eventually is facing the same problem:
First, since I'm using ES6/module imports without Babel I changed the mock function to unstable_mockModule, and then based on the docs I decided to try dynamic imports in test scope after mocking the modules.
If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports). To learn more about this and see it in action, see this repo.
The test component works with the following code:
import { jest } from "#jest/globals";
import { buildReq, buildRes, buildNext } from "../utils/testingHelper";
describe("Live Controller", () => {
test("calls fetchLiveMatches function to fetch from external API", async () => {
jest.unstable_mockModule("../service/live", () => ({
fetchLiveMatches: jest.fn(() => [])
}));
const { getLiveMatches } = await import("./live");
const { fetchLiveMatches } = await import("../service/live");
const req = buildReq();
const res = buildRes();
const next = buildNext(msg => console.log(msg));
await getLiveMatches(req, res, next);
expect(fetchLiveMatches).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.status).toHaveBeenCalledTimes(1);
});
});

Testing NodeJS with Mocha: 'Require is not defined'

EDIT:
As per the comment on the answer below: removing "type": "module" from package.json, which as I understand it is what makes Node understand 'import' and 'export' statements, and reverting everything to 'require' and 'module.exports' solved the issue.
Is there a way to keep 'import' and 'export' and still make Mocha work?
I have a very simple Node file that I'm trying to test with Mocha/Chai. The actual code is trivial, this is just to learn a bit about Mocha and how to use it. But when I run the Mocha test, I get the error ERROR: ReferenceError: require is not defined
`
I did some googling for people experiencing the same problem but the examples that I came up with were when they were running the test in the browser (see, for example, Mocha, "require is not defined" when test in browser).
The file I want to test, index.js
const argv = require('minimist')(process.argv.slice(2));
const digitTester = /\d/g;
const capTester = /[A-Z]/g;
const dict = {
length:10,
must_have_numbers: true,
must_have_caps: true
}
export default function passwordCheck(password) {
if (!password) return false;
if (typeof password !== "string") return false;
if (password.length < dict.length) return false; // assumes that 10 is a MINIMUM length
if (dict.must_have_numbers && !digitTester.test(password)) return false;
return !(dict.must_have_caps && !capTester.test(password));
}
if (argv._.length) {
console.log(passwordCheck(argv._[0]))
}
/**
* alternate version to check a lot of passwords:
*
* if (argv._.length) {
* for (const pwd of argv_) console.log(passwordCheck(pwd)
*}
*
*/
the mocha file, test/index.test.js
const chai = require('chai')
const expect = chai.expect
const passwordCheck = require('../index.js')
const tooShort = "A2a"
const noCaps = "a2abcdefghijklmnop"
const noNumber = "Aabcdefghijklmnop"
const good = "A2abcdefghijklmnop"
describe('password checking', () => {
it('should return false for passwords less than length 10', () => {
expect(passwordCheck(tooShort)).to.be.false;
});
it('should return false for passwords without a capital letter', () => {
expect(passwordCheck(noCaps)).to.be.false;
});
it('should return false for passwords without a number', () => {
expect(passwordCheck(noNumber)).to.be.false;
});
it('should return true for passwords that match criteria', () => {
expect(passwordCheck(good)).to.be.true;
});
});
and package.json
{
"name": "codetest",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "mocha"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"#types/minimist": "^1.2.1",
"#types/node": "^14.14.20",
"chai": "^4.2.0",
"minimist": "^1.2.5",
"mocha": "^8.2.1"
}
}
and the error message is
✖ ERROR: ReferenceError: require is not defined
at file:///Users/r/Documents/Projects/sandbox/pwd_checker/index.js:2:14
at ModuleJob.run (node:internal/modules/esm/module_job:152:23)
at async Loader.import (node:internal/modules/esm/loader:166:24)
at async exports.handleRequires (/Users/r/Documents/Projects/sandbox/pwd_checker/node_modules/mocha/lib/cli/run-helpers.js:94:28)
at async /Users/r/Documents/Projects/sandbox/pwd_checker/node_modules/mocha/lib/cli/run.js:341:25
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Node 15.
Remove this line - "type": "module" from package.json and check whether it’s working or not.
Prepend your tests with the following:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
This is because you cannot require from an ESM module; for more info please see this comment on a nodejs issue.
Documentation: https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs

MongoError: pool is draining, new operations prohibited when using MongoMemoryServer in integration test

I'm using MongoMemoryServer to write an integration test. I have two integration test files. When I run the IT tests I see the following. I don't understand why. I'm using jestjs test framework.
I'm seeing the following error when I have two IT test files
MongoError: pool is draining, new operations prohibited
37 | for (const key in collections) {
38 | const collection = collections[key];
> 39 | await collection.deleteMany();
| ^
40 | }
41 | };
Here is my setup
//db-handler.js
const mongoose = require("mongoose");
const { MongoMemoryServer } = require("mongodb-memory-server");
const mongod = new MongoMemoryServer();
module.exports.connect = async () => {
const uri = await mongod.getConnectionString();
const mongooseOpts = {
useNewUrlParser: true,
autoReconnect: true,
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 1000,
};
await mongoose.connect(uri, mongooseOpts);
};
module.exports.closeDatabase = async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongod.stop();
};
module.exports.clearDatabase = async () => {
const collections = mongoose.connection.collections;
for (const key in collections) {
const collection = collections[key];
await collection.deleteMany();
}
};
All my IT tests setup looks like this
//example.it.test.js
const supertest = require("supertest");
const dbHandler = require("./db-handler");
const app = require("../../src/app");
const request = supertest(app);
const SomeModel = require("../Some");
beforeAll(async () => await dbHandler.connect());
afterEach(async () => await dbHandler.clearDatabase());
afterAll(async () => await dbHandler.closeDatabase());
describe("Some Test Block", () => {
it("Test One", async (done) => {
await SomeModel.create({a: "a", b "b"});
const response = await request.get("/endPointToTest");
expect(response.status).toBe(200);
done();
});
When I have just a single IT test files, everything works fine. When I introduce a a new IT test file similar setup up as example.it.test.js, then the new test fails. The example error message above.
What am I missing? Does my setup need to change when I have multiple IT test files?
UPDATE ONE:
My package.json file looks like
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest --runInBand ./tests"
},
"devDependencies": {
"jest": "^25.2.7",
"mongodb-memory-server": "^6.5.2",
"nodemon": "^2.0.2",
"supertest": "^4.0.2"
},
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
}
I think the error message can be somewhat misleading for some cases.
It seems like the issue is related to "pool size being too small" or "jest runs tests in parallel", so the pool eventually ran out of usable pool.
However, if you look at what happens when mongo destroys the pool, you'll get the idea.
/**
* Destroy pool
* #method
*/
Pool.prototype.destroy = function(force, callback) {
...
// Set state to draining
stateTransition(this, DRAINING);
...
If you try to close the connection, the pool changes its internal state into 'draining' which causes the problem.
So what the error message is telling us is that there's still unfinished writing operation(s) going on after pool.destroy.
For my case, that was an active event listener callback runs every x seconds which does write to mongo.
I changed my test code to call event callback function directly instead of being called every x seconds.
That resolved my issue.
By default Jest runs tests in parallel with a “a worker pool of child processes that run tests” (Jest CLI docs). As per the Jest documentation.
Each test file is making a new connection to the mogodb server which is causing the error.
You could run the Jest test sequentially to avoid this issue.
The --runInBand or -i option of the Jest CLI allows you to run tests sequentially (in non-parallel mode).
This error (pool is draining, new operations prohibited) might occur if you are using the official mongodb library for node js or mongoose. Please specifying pool size while establishing mongodb connection.
module.exports.connect = async () => {
const uri = await mongod.getConnectionString();
const mongooseOpts = {
useNewUrlParser: true,
autoReconnect: true,
reconnectTries: Number.MAX_VALUE,
reconnectInterval: 1000,
poolSize: 10,
};
await mongoose.connect(uri, mongooseOpts);
};
I did the same way and faced into the same error.
This was resolved by removing by schema name in test only like I did below in some.test.js
** Rest all setup remains same **
//example.it.test.js
const dbHandler = require("./db-handler");
const SomeModel = require("../Some");
const SomeOtherModel = require("../SomeOther");
beforeAll(async () => await dbHandler.connect());
afterEach(async () => {
await SomeModel.remove({});
await SomeOtherModel.remove({});
});
afterAll(async () => await dbHandler.closeDatabase());
describe("Some Test Block", () => {
it("Test One", async (done) => {
await SomeModel.create({a: "a", SomeOther: 'SomeOther Data' });
const data = await SomeModel.getAll();
expect(data.length).toBe(1);
done();
});
});
For every It test case, it connects to the mongoose, and if any error occurred, mongoose does not disconnect.
Solution
You can put the it test case in the try catch block, but if possible use 2 point
Try to handle the error in the controllers and service, and so on
files if possible.
And whenever you write test case, there should be no error comes from the development code.
Don't write nested describe in a single describe, max 2 nested
describe we should use.
One temporary solution, I skip the all describe expect outmost where mongoose was connected.
One more temporary solution, disconnect the mongod and then see the status, then reconnect.

Issue with npm require

I'm running a Nightmare.js script, and within it I'm trying to require lodash
(installed locally with npm, in node_modules, registered in package.json).
index.js :
var Nightmare = require('nightmare');
var _ = require('lodash');
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function getElms(elm) {
var elsm = document.querySelectorAll(elm);
return _.map(elsm, function(elem){ return elem.innerHTML; });
}
var someWebsite = new Nightmare({ show: false })
.goto( ***some url*** )
.wait(getRandomInt(2500, 5000))
.evaluate(function () {
var elsm = document.querySelectorAll(***some selector***);
return _.map(elsm, function(elem){ return elem.innerHTML; });
// return getElms(***some selector***);
}).then(function(linkstexts) {
console.log(linkstexts)
});
but I'm getting this:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): _ is not defined
similar issue if I uncomment and use return getElms(***some selector***);
This is package.json
{
"name": "nightxp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"lodash": "^4.17.4",
"nightmare": "^2.10.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
What am I missing?
UPDATE
I tried passing the lodash to the browser scope, and completing with catch(),
but somehow the lodash variable still isnt being passed to the scope (example with hackernews):
var Nightmare = require('nightmare');
var lodash = require('lodash');
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
var facebook = new Nightmare({ show: false })
.goto('https://news.ycombinator.com')
.wait(getRandomInt(5500, 6000))
.evaluate( (lodash) => {
var elsm = document.querySelectorAll('tr td.title a.storylink');
return lodash.map(elsm, function(elem){ return elem.innerHTML; });
}, lodash).then(function(titles) {
console.log(titles)
}).catch(function (error) {
console.error('Error:', error);
});
and this is what I get:
C:\Projects\nightmare>set DEBUG=nightmare & node index.js
nightmare queuing process start +0ms
nightmare queueing action "goto" for https://news.ycombinator.com +4ms
nightmare queueing action "wait" +2ms
nightmare queueing action "evaluate" +0ms
nightmare running +1ms
Error: Cannot read property 'map' of undefined
When you run
var someWebsite = new Nightmare({ show: false })
.evaluate(function () {
/* ... */
}
You are now in browser scope — you can think of Nightmare.evaluate as running the contained code in the context of an independent browser, which means your global scope, including _ is not available.
See here for a bit more info and some tips on passing data into the scope:
https://github.com/segmentio/nightmare/issues/89
https://github.com/segmentio/nightmare/issues/333
Edit:
While you can pass simple values into evaluate from the node context, you can't pass complex values with functions and methods. This is because the arguments are serialize in transit and this is difficult with functions. Here's an issue that explains it a bit:
https://github.com/segmentio/nightmare/issues/349
In this particular case it seems like the easiest thing to do is to work without lodash, which these days isn't hard. For example:
var facebook = new Nightmare({ show: false })
.goto('https://news.ycombinator.com')
.wait(getRandomInt(5500, 6000))
.evaluate( () => {
var elsm = document.querySelectorAll('tr td.title a.storylink');
return Array.prototype.map.call(elsm, elem => elem.innerHTML)
})
.then(function(titles) {
console.log("title", titles)
})
.catch(function (error) {
console.error('Error:', error);
});

Why is my module not appearing in require.cache?

OS: Windows 10
Node version: 0.10.36
Mocha global version: 1.21.4
I'm attempting to use mocha to unit-test my code, but a local variable inside the code I'm trying to test is persisting between tests, causing problems.
When I look inside require.cache, between tests, I don't see my module in there. It is my understanding that I should be clearing the cache if I want to reset this module between tests.
I made a small node project to demonstrate this issue:
package.js:
{
"name": "cache-test",
"version": "0.0.1",
"dependencies": {
"lodash": "4.5.0"
},
"devDependencies": {
"chai": "1.9.2",
"mocha": "1.21.4",
"mockery": "1.4.0",
"sinon": "1.10.3",
"app-root-path":"*"
}
}
module.js:
var foo = "default value";
exports.init = function(){
foo = 'init';
}
exports.returnFoo = function(){
return foo;
}
test/test-module.js
var chai = require("chai"),
expect = chai.expect,
mockery = require("mockery"),
appRoot = require('app-root-path');
var module;
describe("module", function () {
before(function () {
mockery.enable({ useCleanCache: true });
});
beforeEach(function () {
mockery.registerAllowable(appRoot + "/module", true);
module = require(appRoot + "/module");
});
afterEach(function () {
console.log('deleting', require.cache[require.resolve(appRoot + "/module")]);
delete require.cache[require.resolve(appRoot + "/module")];
module = null;
mockery.deregisterAll();
});
after(function () {
mockery.disable();
});
describe("test",function(){
it("foo should be 'init' after running init()",function(){
module.init();
console.log('foo is ',module.returnFoo());
expect(module.returnFoo()).to.equal('init');
});
it("foo should be 'default value' if init() is not run",function(){
console.log('foo is ',module.returnFoo());
expect(module.returnFoo()).to.equal("default value");
});
});
});
running mocha prints
module
test
foo is init
√ foo should be 'init' after running init()
deleting undefined
foo is init
1 failing
Oh, I needed to add
mockery.resetCache() to my afterEach function. That solved it.
It seems like the useCleanCache option, and deleting the entry from require.cache aren't compatible with each-other, as the former keeps it from appearing in the latter.
So it's either:
Don't use useCleanCache
Delete it "manually" from require.cache
OR
Use useCleanCache
Use resetCache()
but don't attempt to mix and match.

Resources