I want to test function which manipulate my DOM. Thus I use jsdom + jest.
If I import the function, then the DOM loads not correctly and I receive a TypeError. The Error tells my that an element is not defined. Which would be defined in the beginning of my main.js.
If I don't import the function, then I could not run the test, because the function is not defined.
the test.js:
const { JSDOM,VirtualConsole} = require('jsdom')
//the function i want to import
const round = require("./main.js").round;
describe('calculate', () => {
let dom
let document
//load the DOM before all tests
beforeAll(async () => {
dom = await initDom('index.html')
document = dom.window.document
})
//my test to test the "round"-function
test(`round`, () => {
expect(round(0.12345)).toEqual(0.12)
})
})
important part of main.js:
module.exports = {
round: round,
}
const pongbar_right = document.getElementById("pongbar_right");
pongbar_right.style.top = 250;
-> here is be the Error: TypeError: Cannot read property 'style' of null
initDom:
const {JSDOM, VirtualConsole} = require('jsdom')
global.initDom = (htmlFile) => {
const virtualConsole = new VirtualConsole();
virtualConsole.on('log', (...args) => {console.log(`&${args}`)})
return JSDOM.fromFile(htmlFile, {
resources: 'usable',
runScripts: "outside-only",
virtualConsole
})
}
Related
I have written a test case that successfully load files into virtual FS, and at the same time mounted a virtual volume as below
describe("should work", () => {
const { vol } = require("memfs");
afterEach(() => vol.reset());
beforeEach(() => {
vol.mkdirSync(process.cwd(), { recursive: true });
jest.resetModules();
jest.resetAllMocks();
});
it("should be able to mock fs that being called in actual code", async () => {
jest.mock("fs", () => {
return ufs //
.use(jest.requireActual("fs"))
.use(createFsFromVolume(vol) as any);
});
jest.mock("fs/promises", () => {
return ufs //
.use(jest.requireActual("fs/promises"))
.use(createFsFromVolume(vol) as any);
});
const { createFsFromVolume } = require("memfs");
const { ufs } = require("unionfs");
const { countFile } = require("../src/ops/fs");
vol.fromJSON(
{
"./some/README.md": "1",
"./some/index.js": "2",
"./destination": null,
},
"/app"
);
const result = ufs.readdirSync(process.cwd());
const result2 = ufs.readdirSync("/app");
const result3 = await countFile("/app");
console.log({ result, result2, result3 });
});
});
By using ufs.readdirSync, I can access to virtual FS and indeed result giving me files that loaded from disc into virtual FS, result2 representing /app which is a new volume created from vol.fromJSON.
Now my problem is I am unable to get the result for result3, which is calling countFile method as below
import fsPromises from "fs/promises";
export const countFile = async (path: string) => {
const result = await fsPromises.readdir(path);
return result.length;
};
I'm getting error
Error: ENOENT: no such file or directory, scandir '/app'
which I think it's because countFile is accessing the actual FS instead of the virtual despite I've had jest.mock('fs/promises')?
Please if anyone can provide some lead?
This is the function you want to unit test.
//CommonJS version
const fsPromises = require('fs/promises');
const countFile = async (path) => {
const result = await fsPromises.readdir(path);
return result.length;
};
module.exports = {
countFile
}
Now, how you would normally go about this, is to mock fsPromises. In this example specifically readdir() since that is the function being used in countFile.
This is what we call: a stub.
A skeletal or special-purpose implementation of a software component, used to develop or test a component that calls or is otherwise dependent on it. It replaces a called component.
const {countFile} = require('./index');
const {readdir} = require("fs/promises");
jest.mock('fs/promises');
beforeEach(() => {
readdir.mockReset();
});
it("When testing countFile, given string, then return files", async () => {
const path = "/path/to/dir";
// vvvvvvv STUB HERE
readdir.mockResolvedValueOnce(["src", "node_modules", "package-lock.json" ,"package.json"]);
const res = await countFile(path);
expect(res).toBe(4);
})
You do this because you're unit testing. You don't want to be dependent on other functions because that fails to be a unit test and more integration test. Secondly, it's a third-party library, which is maintained/tested by someone else.
Here is where your scenario applies. From my perspective, your objective isn't to test countFile() rather, to test fsPromises and maybe test functionality to read virtual file-systems: unionfs. If so then, fsPromises doesn't need to really be mocked.
I intend to test fatch with mocha. I am using testing fetch with mocha and chai
to get started but kept running into multiple errors.
Here is the test code:
var jsdom = require('mocha-jsdom')
global.document = jsdom();
const chai = require( 'chai' ).assert;
const fetch = require("node-fetch");
const chaiHTTP = require("chai-http");
const index = require('../client/js/index');
chai.should();
chai.use(chaiHTTP)
describe('Index', function(){
it( "renders book data ", async () =>{
await fetchBooks()
.then(() => {
expect(renderBooks).to.have.been.called();
})
})
})
This throws the following error :
TypeError: Cannot read property 'querySelector' of undefined
Probably the selector does not return any elements at the stage when the script is executed. Not sure why.
//index.js
function fetchBooks(){
fetch(`https://anapioficeandfire.com/api/books`)
.then(res => res.json())
.then(renderBooks)
}
const main = document.querySelector('#main')
function renderBooks(data) {
data.forEach(book => {
h2.innerHTML = `<h2>${book.name}</h2>`
})
}
I want to implement page object and to be more specific want to read element declared in function in it's prototype.
Tried different ways and those are working fine, however curious if implementation is possible this way also.
Spec File
var login_page = require('../pages/login_page');
describe('login page and properties', function () {
it('verify application launch with URL', function () {
browser.get('appUrl').then(function () {
browser.getTitle().then(function (appTitle) {
expect(appTitle).toBe('Protractor practice website');
});
});
login_page.enterUsername();
});
});
login_page.js:
var login_page = function() {
this.username = element(by.id('username'));
};
login_page.prototype.enterUsername = function() {
this.username.sendKeys('anyString');
};
module.exports = new login_page();
Error:
> protractor conf.js
[22:05:42] E/configParser - Error code: 105
[22:05:42] E/configParser - Error message: failed loading configuration file conf.js
[22:05:42] E/configParser - ReferenceError: element is not defined
Async / await
Think about using async / await. You'll need to change your config SELENIUM_PROMISE_MANAGER: false. This will unchain all the thenables.
Element not defined
Why is element not defined? Probably because the module is loaded before the global object element is defined. Consider instead creating the new object in a beforeAll so we know that element does exist.
Fixes with async / await and element not defined
Spec File:
const LoginPage = require('../pages/login_page').LoginPage;
let login_page = null;
describe('login page and properties', () => {
beforeAll(() => {
login_page = new LoginPage();
});
it('verify application launch with URL', async () => {
await browser.get('appUrl');
const appTitle = await browser.getTitle();
expect(appTitle).toBe('Protractor practice website');
await login_page.enterUsername();
});
});
login_page.js:
If you use JavaScript classes, you don't have to use prototypes.
export class LoginPage {
username = element(by.id('username'));
/**
* enter the user name.
* returns the promise to send keys
*/
async enterUsername() {
return this.username.sendKeys('anyString');
}
}
For example, if I have main.js calling a defined in src/lib/a.js, and function a calls node-uuid.v1, how can I stub node-uuid.v1 when testing main.js?
main.js
const a = require("./src/lib/a").a
const main = () => {
return a()
}
module.exports = main
src/lib/a.js
const generateUUID = require("node-uuid").v1
const a = () => {
let temp = generateUUID()
return temp
}
module.exports = {
a
}
tests/main-test.js
const assert = require("assert")
const main = require("../main")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
assert.equal(main(), "121321")
})
})
The sinon.stub(...) statement doesn't stub uuid.v1 for src/lib/a.js as the above test fails.
Is there a way to globally a library function so that it does the specified behavior whenever it gets called?
You should configure the stub before importing the main module. In this way the module will call the stub instead of the original function.
const assert = require("assert")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
const main = require("../main")
assert.equal(main(), "121321")
})
})
Bear in mind that node-uuid is deprecated as you can see by this warning
[Deprecation warning: The use of require('uuid') is deprecated and
will not be supported after version 3.x of this module. Instead, use
require('uuid/[v1|v3|v4|v5]') as shown in the examples below.]
About how to stub that for testing would be a bit more harder than before as actually there is no an easy way to mock a standalone function using sinon
Creating a custom module
//custom uuid
module.exports.v1 = require('uuid/v1');
Requiring uuid from the custom module in your project
const uuid = require('<path_to_custom_module>');
Sinon.stub(uuid, 'v1').returns('12345');
I'm learning writing unit test with Jest.
I use typescript, but it shouldn't be a problem here. Feel free to provide examples with pure JavaScript.
Until now I have function:
const space = String.fromCharCode(0x0020);
const rocket = String.fromCharCode(0xD83D, 0xDE80);
let notified: boolean = false;
export const logHiring = (message: string = "We're hiring!", emoji: string = rocket) => {
if (!notified) {
console.info(
[message, emoji]
.filter((e) => e)
.join(space)
);
notified = true;
}
};
Yes, function should log to console just one message per initialization.
And not really working tests:
import {logHiring} from "../index";
const rocket = String.fromCharCode(0xD83D, 0xDE80);
// First test
test("`logHiring` without arguments", () => {
let result = logHiring();
expect(result).toBe(`We're hiring! ${rocket}`);
});
// Second test
test("`logHiring` with custom message", () => {
let result = logHiring("We are looking for employees");
expect(result).toBe(`We are looking for employees ${rocket}`);
});
// Third test
test("`logHiring` multiple times without arguments", () => {
let result = logHiring();
result = logHiring();
result = logHiring();
expect(result).toBe(`We're hiring! ${rocket}`);
});
I have two problems:
How can I test console logs? I've tried spyOn without succes.
How can I reset internal (from function) notified variable for each test?
How can I test console logs? I've tried spyOn without succes.
https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
const spy = jest.spyOn(console, 'log')
logHiring();
expect(spy).toHaveBeenCalledWith("We're hiring!")
How can I reset internal (from function) notified variable for each test?
export a getter/setter function like
// index.js
export const setNotified = (val) => { notified = val }
export const getNotified = _ => notified
// index.test.js
import { getNotified, setNotified } from '..'
let origNotified = getNotified()
beforeAll(_ => {
setNotified(/* some fake value here */)
...
}
afterAll(_ => {
setNotified(origNotified)
...
}