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
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?
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.
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 => {...}
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.
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,
};