Nodejs: import class and its constructor in jest tests - javascript

I am noob with JS and I can't figure how to instantiate one of my objects in this jest unit test (backend / nodejs project)
project structure:
appi/
src/
configFactory.js
...
test/
configFactory.test.js
...
package.json
Using require
configFactory.js
class ConfigFactory {
constructor(index_mapping){
this.mapping = index_mapping
}
}
configFactory.test.js
const ConfigFactory = require('../src/configFactory.js')
var fs = require('fs');
test('some test', () => {
fs.readFile(__dirname +'/__mock-data__/Mappings/mappings_ac.json', 'utf8', function(err, data) {
if (err) throw err;
const factory = new ConfigFactory(data);
});
});
This ends up with a TypeError: ConfigFactory is not a constructor
Using import
class ConfigFactory {
constructor(index_mapping){
this.mapping = index_mapping
}
}
export default ConfigFactory
import ConfigFactory from "../src/configFactory"
ends up with SyntaxError: Cannot use import statement outside a module. I tried to add "type": "module" to package.json but I feel that I am missing an important point

Looks like I was not properly exporting my class for CJS:
class ConfigFactory {
constructor(index_mapping){
this.mapping = index_mapping
}
}
module.exports = ConfigFactory

Related

Exporting functions from a simple library which imports from other libraries

I have a simple repository "logger" with an index.js file with the following content:
exports.log = function (message) {
console.log('LOG: ' + message)
}
It works as expected when I publish the repository, and add it as a dependency in a node.js repository like:
import { log } from '#mystuff/logger'
export async function logIt(){
log('Stuff')
}
But when I try to update the logger repository with a dependency on another repo Sentry, as below, I get an error message:
import * as Sentry from "#sentry/node"
exports.log = function (message) {
console.log('LOG: ' + message)
}
exports.notify = function () {
Sentry.captureMessage(`notify?`)
}
Error message:
import * as Sentry from "#sentry/node"
^^^^^^
SyntaxError: Cannot use import statement outside a module
I've tried adding "type": "module" to my logger package.json, but that seems to require dynamic imports which I do not want to use.
How do I include Sentry in my logger repo?

How to test yargs application using Jest (Javascript/Typescript)

I already asked the question on Jest repository here. And also pushed a sample application here to reproduce the behavior. But for the sake of completeness here's the full story:
Essentially it's like this (./parsers.ts):
import yargs from "yargs";
export const parser = yargs
.strict(true)
.help()
.commandDir("cmds")
.demandCommand(1)
.recommendCommands();
And in cmds folder, there's a remote.ts:
import { Argv } from "yargs";
export const command = "remote <command>";
export const describe = "Manage set of tracked repos";
export const handler = (yargs: Argv<any>) => {};
export const builder = (yargs: Argv<any>) => {
return yargs
.commandDir("remote_cmds")
.demandCommand(1, 1)
.recommendCommands();
};
And then there's add.ts:
import { Argv } from "yargs";
export const command = "add <name> <url>";
export const handler = (yargs: Argv<any>): void => {};
export const describe = "Add remote named <name> for repo at url <url>";
export const builder = (yargs: Argv<any>): Argv => {
return yargs.demandCommand(0, 0);
};
Now I've got two more files:
// index.ts
import { parser } from "./parsers";
import { Arguments } from "yargs";
parser.parse("remote add foo", (err, argv, output) => {
console.log("parsed argv: %s", JSON.stringify(argv));
if (err) console.log("ERROR\n" + err);
if (output) console.log("OUTPUT\n" + output);
});
When I run this, it fails, rightly so. Because remote add command expects two arguments. And if I pass correct input, it gives correct output. Meaning everything works just fine.
// parsers.test.ts
import { Arguments } from "yargs";
import { parser } from "./parsers";
describe("remote", () => {
test("add", async () => {
const argv = parser.parse("remote add foo", (err, argv, output) => {
console.log(JSON.stringify(argv));
if (err) console.log("ERROR\n" + err);
if (output) console.log("OUTPUT\n" + output);
});
expect(argv.name).toEqual("foo");
});
});
Also the Jest configuration is:
module.exports = {
transform: {
"^.+\\.ts?$": "ts-jest",
},
testEnvironment: "node",
testRegex: "./src/.*\\.(test|spec)?\\.(ts|ts)$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
roots: ["<rootDir>/src"],
};
But when I run the above test, it doesn't fail at all, as if the parser has no configuration. (The assertion interestingly fails because foo is not extracted as a property into argv which shows, again, the parser didn't pick up the configuration inside cmds folder.)
Not sure if it's a bug or feature; while testing yargs parsers, something is messing with the parser configuration so that, nothing from commands directories gets loaded into the parser.
How can I test my parser using Jest? Thanks.

Cannot read from bundled Database

I bundle a pre-populated sqlite-database in the asset/sqlite/ folder of my project. I edited the metro.config.js in my root folder of the app like this
const { getDefaultConfig } = require('#expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = {
resolver: {
assetExts: [...defaultConfig.resolver.assetExts, 'db'],
},
};
I then try to read the database like this
import * as SQLite from 'expo-sqlite';
import * as FileSystem from 'expo-file-system';
import { Asset } from 'expo-asset';
async function openDatabase() {
// check if folder exists
if (!(await FileSystem.getInfoAsync(FileSystem.documentDirectory + 'sqlite')).exists) {
// if folder does not exist, create it
await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'sqlite');
}
await FileSystem.downloadAsync(
// grab database from asset folder
Asset.fromModule(require('../../../assets/sqlite/foo.db')).uri,
// move to new folder for application to work with it
FileSystem.documentDirectory + 'sqlite/foo.db'
)
return SQLite.openDatabase('foo.db');
}
export function savePoints(location) {
...
const somedb = openDatabase();
somedb.transaction(tx => {...}
}
But that gives me the following
[Unhandled promise rejection: Error: Directory for 'file:///Users/.../Library/Developer/CoreSimulator/Devices/6CD71445-E39B-430A-9691-B174D6300E9A/data/Containers/Data/Application/B543CDCB-6D7E-47D2-ABDD-411FCE115C4C/Documents/ExponentExperienceData/%2540anonymous%252Fmapstar-b773f7ad-9cee-4848-8e06-82f7ca69effc//sqlite/foo.db' doesn't exist.
Please make sure directory '/Users/.../Library/Developer/CoreSimulator/Devices/6CD71445-E39B-430A-9691-B174D6300E9A/data/Containers/Data/Application/B543CDCB-6D7E-47D2-ABDD-411FCE115C4C/Documents/ExponentExperienceData/%40anonymous%2Fmapstar-b773f7ad-9cee-4848-8e06-82f7ca69effc/sqlite' exists before calling downloadAsync.]
and
[Unhandled promise rejection: TypeError: undefined is not a function (near '...foo.db.transaction...')]
Why is that? The database exists, the path is correct too.
Because openDatabase() is an async function, the result must be awaited.
Instead of
const somedb = openDatabase();
somedb.transaction(tx => {...}
do
somedb.then(db => {...}

Add new extension to nodejs dynamic import

How do I add an new file extension to Nodejs dynamic import?
I want to add my own filetype, lets call it .jszip. (No, this is just an example and what I actually want has nothing to do with zip).
Say I have
package.json:
{
"name": "test",
"scripts": {
"zip": "node --experimental-modules test.js"
}
}
test.js:
const fs = require('fs');
const Module = require('module');
function loadJsZip(module, filename) {
console.log('In loadJsZip');
const content = fs.readFileSync(filename, 'utf8');
// Do something to content
module._compile(content, filename);
}
require.extensions['.jszip'] = loadJsZip;
Module._extensions['.jszip'] = loadJsZip;
function loadJs(relativePath) {
import(f).then((module) => {
console.log(`imported from ${filename}:${module}`);
}).catch((err) => {
console.log(`While importing:${err}`);
});
}
loadJs('./testfile.jszip');
I am getting:
(node:20412) ExperimentalWarning: The ESM module loader is experimental.
While importing:TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension: c:\...\testfile.jszip
It seems other file types are not supported: https://nodejs.org/api/esm.html#esm_import_statements
What worked for my case is getting the normal require and using that. So I'm importing .graphql files using:
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
require('graphql-import-node/register');
const myQuery = require('./myquery.graphql');
The graphql-import-node package does a require.extension[] = behind the scenes:
require.extensions['.graphql'] = loadPlainFile
But this is starting to become madness. You are probably better off using Webpack or something.

Raven throws warnings when testing via Jest

I'm trying to test my GraphQL api through Jest and every time I run my tests I keep getting this alert:
raven#2.5.0 alert: This looks like a browser environment; are you sure you don't want Raven.js for browser JavaScript?
The cause:
I create a custom Error class that inherits from Error:
import logError from './errors';
class LoggedErrorClass extends Error {
constructor(error) {
logError(error);
const prototype = new.target.prototype;
if (typeof error === 'string') {
super(error);
} else {
super(error.message);
}
this.__proto__ = prototype;
}
}
LoggedError = LoggedErrorClass;
And use it like this:
if (!user || !Roles.userIsInRole(user._id, ['admin', 'customer'])) {
throw new LoggedError('Access denied');
}
logError is a function that uses Raven. Because I use Meteor I do LoggedError = LoggedErrorClass to make LoggedError accessible globally (notice, I don't export LoggedErrorClass)
My test looks like this:
import { graphql } from 'graphql';
import schema from '../../../graphql';
describe('getMobileSettings query', function() {
// global.LoggedError = class extends Error {
// constructor(...args) {
// super(...args);
// Error.captureStackTrace(this, Error);
// }
// };
it('should work', async () => {
const query = `
query getMobileSettings($app: String!) {
getMobileSettings(app: $app)
}`;
const [rootValue, context, params] = [{}, {}, { app: 'web' }];
await graphql(schema, query, rootValue, context, params);
});
});
I've tried setting LoggedError with the help of global but it didn't help. So, I can't just call jest.mock('path/to/file') because I don't export it. Also, it seems quite weird that Raven is here, because I use it in logError which I only import in a file where I create LoggedErrorClass
Ok, after some digging, I figured out the solution.
I decided not to mock LoggedError class but rather mock logError function that my class uses. As a result I came up with this code that mocks Raven behaviour:
const Raven = {};
const install = jest.fn();
const config = jest.fn();
Raven.install = install;
Raven.config = config;
// mocking chained function calls
install.mockImplementation((...args) => {
return Raven;
});
config.mockImplementation((...args) => {
return Raven;
});
export default Raven;
I've also updated my jest.conf.js by adding raven to moduleNameMapper:
module.exports = {
moduleNameMapper: {
'^meteor/(.*)': '<rootDir>/tests/.mocks/meteor/index.js',
raven: '<rootDir>/tests/.mocks/npm/raven.js',
},
automock: false,
clearMocks: true,
};

Categories