I'm trying to unit test this class that has a dependency of AppDB and createStudy that I need to mock. To get started I'm attempting to unit test the simple method startLoadingData which happens to be a MobX action
import { observable, action } from 'mobx'
import { Intent } from '#blueprintjs/core'
import { createStudy } from '../database/DatabaseInit'
import { AppDB } from '../database/Database'
export default class UIStore {
// ui state
// booleans indicating open/close state of modals
#observable createDialogue
#observable importDialogue
#observable revisionsDialogue
#observable runDialogue
// boolean indicating loading or waiting for async action
#observable loadingData
// array indicating navigation
#observable breadcrumbs
#observable processingMessages
constructor(rootStore) {
this.rootStore = rootStore
this.breadcrumbs = []
this.importDialogue = false
this.createDialogue = false
this.revisionsDialogue = false
this.runDialogue = false
// boolean to display loading blur on table that displays data
this.loadingData = false
// processing messages for import and other async loads
this.processingMessages = []
}
#action startLoadingData() {
this.loadingData = true
}
}
My test file below is getting nowhere because there's an error being thrown related to a separate dependency of sqlite3 in the AppDB and createStudy imports. My understanding is that if I mock those two dependencies that I can avoid the error because they'll be mocked and not real implementations trying to use sqlite3.
// UIStore domain store unit test
// import * as Database from '../../app/database/Database'
// import * as DatabaseInit from '../../app/database/DatabaseInit'
import UIStore from '../../app/stores/UIStore'
describe('UIStore', () => {
beforeEach(() => {
// jest.spyOn(Database, 'AppDB').andReturn('mockAppDB')
// jest.spyOn(DatabaseInit, 'createStudy').andReturn('createStudy')
jest.mock('../../app/database/Database')
// jest.mock('DatabaseInit')
})
it('starts loading data', () => {
const testUIStore = new UIStore(this)
testUIStore.startLoadingData()
expect(testUIStore.loadingData).toBe(true)
})
})
As you can see, trying a bunch of things, but I don't seem to be getting anywhere. I've read about manual mocks, and thought that might be the case so I made a manual mock of Database but not even sure if I'm doing that correctly.
const Database = jest.genMockFromModule('../Database.js')
module.exports = Database
I dont think this matters, but it might be worth noting that AppDB is a ES6 class and createStudy is a method.
Jest should auto mock modules from node_modules if you create a __mocks__ folder in your root project folder and create in that folder mocks for the modules you want auto mocked. By auto mock I mean that when writing a test and Jest detects that folder it will automatically load the mock instead of the original module. This also applies to dependencies of dependencies.
So in your case I would try to create a sqlite3 like so:
/project
|
-> __mocks__
| |
| -> sqlite3/index.js <- export mocked functions
|
-> node_modules
At least this is how I deal with libraries in my Jest tests.
Hope this helps.
Related
I'm operating a bot on Wikipedia using npm mwbot, and planning to migrate to npm mwn. This is because you need a "token" to edit pages on Wikipedia, and this can expire after a while so you need to prepare your own countermeasures against this if you use mwbot, but it seems like mwn handles this issue on its own.
When you use mwn, you first need to initialize a bot instance as documented on the turotial:
const bot = await mwn.init(myUserInfo);
Then your token is stored in the bot instance and you can for example edit a page using:
const result = await bot.edit('Page title', () => {text: 'text'});
So, basically you want to share the initialized bot instance across modules. I believe it'd be easiest to declare a global variable like so:
// bot.js (main script)
const {mwn} = require('mwn');
const {my} = require('./modules/my');
(async() => {
global.mw = await mwn.init(my.userinfo);
const {t} = require('./modules/test');
t();
})();
// modules/test.js
/* global mw */
exports.t = async () => {
const content = await mw.read('Main page');
console.log(content);
return content;
};
I'm currently using JavaScript, but will hopefully migrate to TypeScript (although I'm new to it) because I feel like it'd be useful in developing some of my modules. But I'm stuck with how I should use the initialized bot instance across modules in TypeScript.
-+-- dist (<= where ts files are compiled into)
+-- src
+-- types
+-- global.d.ts
+-- bot.ts
+-- my.ts
// bot.ts
import {mwn} from 'mwn';
import {my} from './my';
(async() => {
global.mw = await mwn.init(my.userinfo);
})();
// global.d.ts
import {mwn} from 'mwn';
declare global {
// eslint-disable-next-line no-var
var mw: mwn;
}
This doesn't work and returns "Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. (at mw in global.mw)".
This is probably a naive question but any help would be appreciated.
Edit:
Thanks #CertainPerformance, that's a simple and easy approach. Actually, I once tried the same kind of an approach:
export const init = async () => {
if (typeof mw === 'undefined') {
return Promise.resolve(mw);
} else {
return mwn.init(my.userinfo).then((res) => {
mw = res;
return mw;
});
}
}
But I was like "init().then() in every module?"... don't know why I didn't come up with just exporting the initialized mwn instance.
Anyway, is it like the entry point file should be a .js file? I've been trying with a .ts file and this is one thing that's been giving me a headache. I'm using ts-node or nodemon to auto-compile .ts files, but without "type: module", "Cannot use import statement outside a module" error occurs and with that included, "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"" occurs. How do you tell a given file should be a .js or .ts file?
Edit2:
Just making a note: The error I mentioned above was caused by not having "module": "CommonJS" in my tsconfig.json, as I commented to CertainPerformance's answer below.
One of the main benefits of modules is the ability to drop dependencies on global variables. Rather than going back on that and assigning a global anyway, a better approach that happens to solve your problem as well would be to have a module that exports two functions:
One that initializes the asynchronous mwn
One that returns mwn when called
// mw.ts
import {mwn} from 'mwn';
import {my} from './my';
let mw: mwn;
export const init = async () => {
mw = await mwn.init(my.userinfo);
};
export const getMw = () => mw;
Then it can be consumed by other modules quite naturally, barely requiring any typing at all:
// modules/index.ts
// Entry point
import { init } from './mw';
import { test } from './test';
init()
.then(() => {
test();
})
// .catch(handleErrors);
// modules/test.ts
import { getMw } from './bot';
export const test = () => {
const mw = getMw();
// anything else that depends on mw
};
The above could be a reasonable approach if mw is used in many places across your app and you don't think that passing it around everywhere would be maintainable.
If you could pass it around everywhere, that would have even less of a code smell, though.
// initMw.ts
import {mwn} from 'mwn';
import {my} from './my';
export const initMw = () => mwn.init(my.userinfo);
// modules/index.ts
// Entry point
import { initMw } from './initMw';
import { test } from './test';
initMw()
.then((mw) => {
test(mw);
})
// .catch(handleErrors);
// modules/test.ts
import { mwn } from 'mwn';
export const test = (mw: mwn) => {
// anything that depends on mw
};
Initialize it once, then pass it down (synchronously) everywhere it's needed - that's the approach I'd prefer.
You could put the mwn type in a global d.ts file to avoid having to add import { mwn } from 'mwn'; to every module if you wanted. (Yes, it's somewhat of a global variable, but it's a type rather than a value, so it's arguably less of a problem.)
I have written a javascript file that provides configuration, and separately a proxy that lets me provide a fallback value if the property access was unsuccessfull
// stackUrlMap.js
export const stacks = {
'stackoverflow.com': 'StackOverflow',
'cooking.stackexchange.com': 'SeasonedAdvice',
}
// stackUrlConfig.js
import { stacks } from './stackUrlMap';
const getOrParse = parser => ({
get: (target, prop) => target?.[prop] ?? parser(prop);
})
const urlToName = url => url.split('.')[0];
export const stackUrlToNameMap = new Proxy(stacks, getOrParse(urlToName));
This was working perfectly, and I had a test written for it that passed.
import { expect } from 'chai';
import sinon from 'sinon';
import * as urlMap from './stackUrlMap';
describe('stackUrlConfig.js', function() {
let sandbox;
let stackUrlToNameMap = {};
beforeEach(async function() {
sandbox = sinon.createSandbox();
sandbox.stub(urlMap, 'stack').value({'madeup.url.com': 'Made Up Stack'});
// import here so we get the stubbed values passed through.
({ stackUrlToNameMap } = await import('./stackUrlConfig.js'))
});
it('can get an existing prop from the proxied map', function() {
const field = 'madeup.url.com';
// stub working correctly when accessed from test file
expect(urlMap.stacks[field]).to.exist();
// tests that if the proxied map can find the prop it uses that value
expect(stackUrlToNameMap[field]).to.equal('Made Up Stack');
});
})
However, now I've written code that uses stackUrlConfig.js elsewhere, that is imported by my test setup elsewhere, and the dynamically imported proxied map stackUrlToNameMap in the beforeEach of my test is not being 'respected', and the real stacks object is being used for the lookup, meaning I get this AssertionError:
AssertionError: expected 'madeup' to equal 'Made Up Stack'
If I put a console.trace in the stackUrlConfig.js, I can see it's first called before my test runs as it's imported into another file that isn't run by mocha yet.
I don't want to have to use decache (I'm not even sure it would work, I'm not using require) as seen in Re-importing modules between mocha tests if I can avoid it.
I took the code and tried to add a cache busting, like so:
const absolutePath = path.resolve(__dirname, './stackUrlConfig.js');
({ stackUrlToNameMap } = await import(`${absolutePath}?update=${Date.now()}`));
But my mocha setup did not find the resulting module.
I can't control whether this file is imported elsewhere first in my test, so can I reset mocha's reference to the module somehow?
I'm working on a NUXT project and I find myself copying the same actions into multiple store/modules. So I have extracted the actions to a separate file and I now import it into the module. That works great but I occasionally need an additional action that is not boilerplate. How do I import the boilerplate actions and also have a custom action in the module?
Tags module:
import Vue from "vue";
import globalActions from "../utils/Actions.js";
export const state = () => ({ /* removed */ })
export const actions = globalActions;
//Need actions to be a bit more flexible to include non-boilerplate actions
I'm not sure it matters but here is utils/Actions.js It is just the standard "export default {}" that would typically be in the module.
export default {
all({ commit }, all) {
all.data.forEach(item => {
commit("add", item);
});
},
async list({ commit, state, getters, dispatch }) {
/* flush resets the state */
commit("flush");
/*Makes the api call using the repository setup */
let params = getters.params;
const results = await this.$repositories[state.type].index(params);
const ids = results.data.map(item => item.id);
let page = state.page;
dispatch("all", results);
/*Adds a encyclopedia that maps pages to index */
commit("SET_REFERENCE", { page, ids });
commit("totalItems", results.meta.total);
},
}
Ideally, I think the module actions would look something like this:
export const actions = {
list(){ return globalActions.list }
nonBoilerPlateAction({commit})
}
I am pretty sure I will need to change how I import the globalActions and that my "ideal" actions syntax is wrong but I not sure what I need to adjust.
To merge the imported actions and the custom actions, you can merge the two actions in this manner:
export const actions = {
...globalActions,
nonBoilerPlateAction({commit}) {
commit('something')
}
}
Although the way you are using to re-use your module works fine, I'll recommend using namespacing instead. This way all your created module can be easily re-used without having to import them to any other file. You can have access to other modules from another module easily.
I am trying to import a class from one file to another, but when I do import it, it returns undefined for a couple hundred milliseconds, then I get the value that I am looking for:
// commands.js
export class Command {
// ...
}
// cmds/Help.js
import {Command} from './../commands.js'
console.log(Command) // => undefined
setTimeout(() => console.log(Command), 1000) // => [Function: Command]
So, of course, when I try to extend the imported class, I get errors saying that undefined in not a constructor. The only solution that I can see is to wrap everything inside the cmds/ directory with a setTimeout(), but that would not be efficient. I have messed around with using async/await functions and promises, but neither of those solved the issue.
Edit 1
I'm trying to post as much of the requested information as I can, but I am away from my computer so I'm posting all that I can for right now.
The code is running on Node 9.5, and is transpiled by Babel env set to "node": "current".
As for the code, here is all I can give you right now:
// Directory structure
[src]
|- cmds/
| |- Help.js
| |- Gh.js
| \...
|- commands.js
|- data.js
|- send-msg.js
\... several more js and json files
// commands.js
import {readData, updateUserData} from './data.js'
import {sendMsg} from './send-msg.js'
//import * as cmdList from './cmds/*' // using babel-plugin-wildcard
export class Command {
constructor (msg) {
this.id = msg.author.id
this.msg = msg
}
action () {}
get data () {
return readData().user[this.id]
}
updateUserData (key, val) {
updateUserData(this.id, key, val)
}
sendMsg (data) {
sendMsg(this.msg, data)
}
}
// there is an exported function, but I do not have the contents atm
// cmds/Gh.js
// There are several similar files in cmds/, including this one and the one
// mentioned in the question, Help.js; there are more. They all have
// the same issue, though.
import {Command} from '../commands'
export class Gh extends Command {
constructor (msg) {
super(msg)
this.desc = 'Returns GitHub repository link and exits'
}
action () {
this.sendMsg('GitHub link: https://github.com/owm111/knife-wife')
}
}
BTW: the GitHub repository above does not have this code in it yet, but everything that is in it is functioning (i.e. not experiencing this issue).
Update
Per the answer from epiqueras, I looked at how I'm handling the import. First, /models/index.js is exporting named exports. Here's the code:
'use strict';
import { readdirSync } from 'fs'
import {
basename,
extname,
resolve
} from 'path';
//
// Using module.exports here as a bit of a hack
// to allow for member imports in the form of
// import { Constructor } from "~/models"
module.exports = readdirSync(__dirname) // Get contents of current directory
.filter(f => f !== 'index.js') // Exclude the index file
.map(f => resolve(__dirname, f)) // Resolve the complete module file path
.map(f => require(f).default) // Require the module
.reduce((prev, next) => { // Reduce the array of modules to a hash of Module.name = Module
prev[next.name] = next;
return prev;
}, {});
I derived this from the requireindex project which did not work for me (no doubt user error). What I have since discovered is that if I import the class directly, i,e., import Patron from '../models/patron' then everything works as expected.
At this point, I have five other models in my project that all export fine using the code above. Patron is the only one that doesn't. And as stated in the original question, if I change the name to anything else, the code above exports that new name with no issues.
Thankfully, I have a workaround now. Hopefully I can figure out why it's choking on the name Patron.
Original Question
I've written a simple class in JavaScript:
'use strict'
export default class Patron {
constructor(props) {
this.props = props;
}
create() {
// In my actual code I make a network call,
// simplified this just to see if anyone can get it to return a promise
return Promise.resolve(this);
}
}
For completeness, here's an example of how I'm using the constructor:
'use strict'
import { Router } from 'express';
import { Patron } from '../models';
const PatronRouter = Router();
PatronRouter.post('/patrons', (req, res) => {
let patron = new Patron({ name: 'John Doe' });
let promise = patron.create().then(response => res.send(response);
}
export PatronRouter;
And here's what I experience:
Patron is a valid constructor, no errors initializing an instance
patron is an instance of Patron
patron.props is undefinded
patron.create exists as an instance method
patron.create returns undefined
Here's what makes absolutely no sense to me: if I change the name of the class everything works. I'm not understanding where/how/why the name Patron is causing a problem?
A couple of other notes:
Running node (6.9.2)
Part of an Express (latest) app, trying to execute this code from a Router
Using Babel 6 with the es2015 preset enabled
Thoughts?
You're exporting the class as a default export, but importing it as a named export.
Try this: import Patron from '../models;
Or change the export to a named export: export class Patron