Jest Module Mock with ECMAScript Modules - unit-testing

Using the documentation (https://jestjs.io/docs/ecmascript-modules) as a guide, I have written the following:
package.json:
{
"scripts": {
"watch-test": "jest ./test --verbose --watch"
},
"dependencies": {
"ethers": "^5.6.9"
},
"devDependencies": {
"jest": "^28.1.3"
},
"jest": {
"verbose": true,
"transform": {}
},
"type": "module"
}
test/test.js:
import {jest} from '#jest/globals';
import {ethers} from 'ethers';
jest.mock('ethers', () => ({ethers: 'Hello, world!'}));
console.log(ethers);
Use the following to execute the test: npm i && NODE_OPTIONS=--experimental-vm-modules npm run watch-test;. The output is console.log {Signer: [Getter],Wallet: [Getter],... and so on but I wish it said something like console.log "Hello, world!".
It appears that the ethers module (or the ethers object in the module?) is not getting mocked at all. I tried moving the jest.mock call above the import (which shouldn't be necessary because of jest hoisting), but that didn't help anything.
How do I mock the ethers module using jest when using ECMAScript modules configuration?

Although hoisting is done in common JS, it is not done using ECMAScript Modules, so instead of importing mocked modules, you must use dynamic import to import them after the mocking.
test/test.js:
import {jest} from '#jest/globals';
jest.mock('ethers', () => ({ethers: 'Hello, world!'}));
const {ethers} = await import('ethers');
test('do it', ()=> {
expect(ethers).toEqual("Hello, world!");
});
package.json:
{
"scripts": {
"watch-test": "jest ./test --verbose --watch"
},
"dependencies": {
"ethers": "^5.6.9"
},
"devDependencies": {
"jest": "^28.1.3"
},
"jest": {
"verbose": true,
"testMatch": [
"<rootDir>/test/**/*.?(c)js"
],
"transform" : {}
},
"type": "module"
}
("testMatch": "test/**" doesn't work, you must prefix with <rootDir> or **; I'm not sure why.)
And invoked as stated in the question.
This conversation got me on the right track: https://github.com/facebook/jest/issues/13135

Related

Error: Firebase is not configured. Ensure that you have configured 'google-services.json' correctly

After running expo install expo-firebase-core expo-firebase-analytics and downloading both google-services.json and GoogleService-Info.plist from firebase console and placing them on the root of my project.
When i call Analytics.logEvent, expo go gives an error.
Possible Unhandled Promise Rejection (id: 0):
Error: Firebase is not configured. Ensure that you have configured 'google-services.json' correctly.
this is my TopLevelComponent.js:
import React from 'react'
import * as Analytics from 'expo-firebase-analytics';
import { createRootNavigator } from './router'
const RootNavigator = createRootNavigator()
const TopLevelComponent = props => {
const { screenProps } = props;
const { checkLogin } = screenProps;
const getActiveRouteName = navigationState => {
if (!navigationState) {
return null
}
const route = navigationState.routes[navigationState.index]
// Parse the nested navigators
if (route.routes) return getActiveRouteName(route)
return route.routeName
}
return (
<RootNavigator
onNavigationStateChange={async (prevState, currentState) => {
const currentScreen = getActiveRouteName(currentState)
const prevScreen = getActiveRouteName(prevState)
if (prevScreen !== currentScreen) {
checkLogin()
Analytics.logEvent('event')
}
}}
screenProps={props.screenProps}
/>
);
}
export default TopLevelComponent
Am i missing any other config?
Is there any other way to configure firebase-analytics besides this files?
I'm using expo-44.0.6 and expo-firebase-analytics-6.0.1
I had the same error.
This is how I fixed it:
Go to app.js and add
"googleServicesFile": "./GoogleService-Info.plist"
under the "iOS" section.
example:
"expo": {
"name": "",
"slug": "",
"version": "",
"orientation": "",
"icon": "",
"splash": {
"image": "",
"resizeMode": "",
"backgroundColor": ""
},
"updates": {
"fallbackToCacheTimeout":
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet":,
"bundleIdentifier": "",
"googleServicesFile": "./GoogleService-Info.plist"
},
Similar for Android:
"android": {
"googleServicesFile": "./google-services.json",
"adaptiveIcon": {
"foregroundImage": "",
"backgroundColor": ""
}
Add this for under the "web" section:
"web": {
"config": {
"firebase": {
"apiKey": "",
"authDomain": "",
"projectId": "",
"storageBucket": "",
"messagingSenderId": "",
"appId": "",
"measurementId": "G-**********"
}
},
"favicon": "./assets/favicon.png"
}
Then in the app:
import * as Analytics from 'expo-firebase-analytics';
const pageView = async (routeName, params) => { await Analytics.logEvent(routeName, params); };
I had same mistake. In my case i was using the Expo Bare Workflow with the SDK 45.
I only add these line in my android/build.gradle
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
classpath 'com.google.gms:google-services:4.3.10' /* Add this line */
}
And in the android/app/build.gradle on the top file
apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services' /* Add this line */
Clean the project and run: production npx react-native run-android
That´s works for me :)
I was experiencing a similar issue connecting to analytics on Firebase. I had all the configurations mentioned above for web and ios. I wanted to set up analytics for my expo app and only use the expo-firebase-analytics library. I was testing the connection from an ios simulator.
My issue was simply resolved by adding a second app to my Firebase for the ios platform. The GoogleService-Info.plist file was auto-generated in Firebase and available to download and be placed in my project.
Initially, I only added an app to my Firebase for the web platform so I was unable to establish a connection between the ios simulator and the analytics on Firebase.

How to access Vuejs methods to test with Jest?

I have the following file: deposit-form.js.
With the following code:
new Vue({
el: '#app',
data: {
title: 'title',
depositForm: {
chosenMethod: 'online',
payMethods: [
{ text: 'Already paid via Venmo', value: 'venmo' },
{ text: 'Pay online', value: 'online' },
{ text: 'In-person payment', value: 'person' }
],
},
},
methods: {
submitDeposit: function() {
$.ajax({
url: 'http://localhost:8000/api/v1/deposit/',
type:'post',
data: $('#deposit-form').serialize(),
success: function() {
$('#content').fadeOut('slow', function() {
// Animation complete.
$('#msg-success').addClass('d-block');
});
},
error: function(e) {
console.log(e.responseText);
},
});
},
showFileName: function(event) {
var fileData = event.target.files[0];
var fileName = fileData.name;
$('#file-name').text('selected file: ' + fileName);
},
},
});
I'm having problems on how to setup Jest, how to import the VueJs functions inside 'methods' to make the tests with Jest.
How should be my code on the deposit-form.test.js ?
The first thing you need to do is export Vue app instance.
// deposit-form.js
import Vue from 'vue/dist/vue.common';
export default new Vue({
el: '#app',
data: {...},
...
});
Now you can use this code in your spec file. But now you need to have #app element before running tests. This can be done using the jest setup file. I will explain why it's needed. When you import your main file (deposit-form.js) into a test, an instance of Vue is created in your main file with new. Vue is trying to mount the application into #app element. But this element is not in your DOM. That is why you need to add this element just before running the tests.
In this file you also can import jQuery globally to use it in your tests without import separately.
// jest-env.js
import $ from 'jquery';
global.$ = $;
global.jQuery = $;
const mainAppElement = document.createElement('div');
mainAppElement.id = 'app';
document.body.appendChild(mainAppElement);
Jest setup file must be specified in the jest configuration section in package.json.
// package.json
{
...,
"dependencies": {
"jquery": "^3.3.1",
"vue": "^2.6.7"
},
"devDependencies": {
"#babel/core": "^7.0.0",
"#babel/plugin-transform-modules-commonjs": "^7.2.0",
"#babel/preset-env": "^7.3.4",
"#vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.1.0",
"babel-loader": "^8.0.5",
"babel-preset-vue": "^2.0.2",
"jest": "^24.1.0",
"vue-jest": "^3.0.3",
"vue-template-compiler": "^2.6.7",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3"
},
"scripts": {
"test": "./node_modules/.bin/jest --passWithNoTests",
"dev": "webpack --mode development --module-bind js=babel-loader",
"build": "webpack --mode production --module-bind js=babel-loader"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
"setupFiles": [
"<rootDir>/jest-env.js"
]
}
}
Also, you probably need to configure Babel to use the features of ES6 in your projects and tests. This is not necessary if you follow the commonjs-style in your code. Basic .babelrc file contains next code:
// .babelrc
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "entry",
"targets": {
"browsers": [
"last 2 versions"
]
}
}
],
"vue",
],
"plugins": [
"#babel/plugin-transform-modules-commonjs",
]
}
Now you can write your tests.
// deposit-form.test.js
import App from './deposit-form';
describe('Vue test sample.', () => {
afterEach(() => {
const mainElement = document.getElementById('app');
if (mainElement) {
mainElement.innerHTML = '';
}
});
it('Should mount to DOM.', () => {
// Next line is bad practice =)
expect(App._isMounted).toBeTruthy();
// You have access to your methods
App.submitDeposit();
});
});
My recommendation is to learn Vue Test Utils Guides and start to divide your code into components. With the current approach, you lose all the power of components and the ability to test vue-applications.
I updated my answer a bit. As I understood from the comment to the answer, you connect the libraries on the page as separate files. Here is my mistake. I didn't ask if the build system is being used. Code in my examples is written in the ECMA-2015 standard. But, unfortunately, browsers do not fully support it. You need an transpiler that converts our files into a format that is understandable for browsers. It sounds hard. But it's not a problem. I updated the contents of the file package.json in response. Now it only remains to create an input file for the assembly and run the assembly itself.
The input file is simple.
// index.js
import './deposit-form';
The build is started with the following commands from terminal.
# for development mode
$ yarn run dev
# or
$ npm run dev
# for production mode
$ yarn run build
# or
$ npm run build
The output file will be placed in the directory ./dist/. Now instead of separate files you need to connect only one. It contains all the necessary for the library and your code.
I used webpack to build. More information about it can be found in the documentation. Good example you can find in this article.

Exporting webpack config as a function fails

I'am trying to handle development and production eviroment variables in my webpack configuration (see https://webpack.js.org/guides/production/), but it fails with
WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration should be an object.
package.json
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "./node_modules/.bin/webpack",
"start": "npm run build && node server.js"
},
"devDependencies": {
//...
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-dev-middleware": "^3.4.0",
"webpack-hot-middleware": "^2.24.2"
}
}
webpack.config.js
const path = require('path'),
webpack = require('webpack'),
HtmlWebpackPlugin = require('html-webpack-plugin');
let config = {
entry: {
app: [
'./src/app/App.tsx', 'webpack-hot-middleware/client'
],
vendor: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].bundle.js'
},
// ...
}
This export is working as expected without errors or warnings
module.exports = config; // everything is fine
But this fails
module.exports = function(env, argv) { // this errors
return config;
};
There is a similiar, but unanswered question here: webpack base config as a function doesn't work
It's a very mysterious behaviour, appreciate if anyone could help!
Well,it is working. I didn't notice that the error takes place on a total different spot of my code.
I was doing a tutorial about HMR with webpack and express. An it's this lines of code in the express setup which causes the trouble:
server.js
const webpackConfig = require('./webpack.config');
const compiler = webpack(webpackConfig);
//...
app.use(
require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: webpackConfig.output.publicPath
})
);
The webpackConfig is only getting a function without being called and so it's not returning an object. So adding parenthesis is all it took to make it work.
const webpackConfig = require('./webpack.config')();
//..
The documentation is a bit quirky. You properly forgot the set the env variable from package.json
For instance "start": "webpack --env.prod --", in package.json will pass {prod: true} as the env variable.
Hope this helps.
More info here: https://webpack.js.org/api/cli/#environment-options

Chai Testing Framework Not Giving Message for Passing Unit Test

Here is my code:
const chai = require('chai')
describe('add',function(){
chai.should()
const result = 6
result.should.be.a('number')
result.should.equal(6)
})
When I run node test I get the following:
> mocha
0 passing (5ms)
package.json:
{
"name": "test-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"author": "",
"license": "ISC",
"dependencies": {
"chai": "^4.0.2",
"mocha": "^3.4.2"
},
"devDependencies": {
"chai":"*",
"mocha":"*"
}
}
The way to tell Mocha you have a test is to put your test as a callback to the it function that Mocha makes available. describe groups tests but does not define any test. So if you have a describe without it, then you have no test.
Chai has no impact on how Mocha operates. Mocha does not care that Chai is there and that you are using it. You can use Mocha with any assertion library that raises an exception or rejects a promise (and return the promise from your test) when an assertion fails. Chai is one such assertion library. And you could use Chai with another test runner than Mocha.

Accessing angular service constructor in Jest testing framework

I'm struggling to find an answer to the following (and I'll bet it's more than likely that the answer is relatively straightforward)...
I'm unit testing an Ionic 3.4 application using the Jest testing framework and can't figure out how to call an angular service for testing where the constructor initialises the Http object and an InAppBrowser Ionic Native plugin object.
My package.json file contains the following configuration:
...
"jest": {
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
]
},
"devDependencies": {
"#types/jest": "^20.0.2",
"#types/node": "^7.0.31",
"jest": "^20.0.4",
"ts-jest": "^20.0.6",
"ts-node": "^3.0.6",
"tslint": "^5.4.3",
"tslint-eslint-rules": "^4.1.1",
"typescript": "2.3.3"
},
...
My tsconfig.json file, located in the src/unit-tests directory (so as to only target that directory and its contents and not interfere with the root tsconfig.json file for the Ionic project), is as follows:
{
"compilerOptions" : {
"module" : "commonjs",
"allowSyntheticDefaultImports" : true,
"experimentalDecorators" : true,
"noImplicitAny" : false,
"removeComments" : false,
"locale" : "en-GB",
"alwaysStrict" : true,
"skipLibCheck" : true,
"pretty" : true
}
}
The requested service - providers/settings/settings.ts - is as follows:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { InAppBrowser } from '#ionic-native/in-app-browser';
import 'rxjs/add/operator/map';
#Injectable()
export class SettingsProvider {
constructor(public http : Http,
private _INAPP : InAppBrowser)
{ }
// Methods follow here
}
This is then used within the settings.test.ts file like so:
import 'reflect-metadata';
import { SettingsProvider } from '../providers/settings/settings';
let settings = null;
beforeEach(() => {
// The following works IF the SettingsProvider class
// contains NO parameters in the constructor
settings = new SettingsProvider();
});
describe('Settings service', () =>
{
// Tests are defined within here
// using methods supplied by the
// SettingsProvider class
});
IF I take the parameters out of the SettingsProvider class constructor Jest can run the tests exactly as intended (pass or fail).
With the parameters in place inside the SettingsProvider class constructor Jest chokes when trying to run the test script with the following error:
Test suite failed to run
/ionic-unit/node_modules/#ionic-native/in-app-browser/index.js:20
import { Injectable } from '#angular/core';
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/ScriptTransformer.js:289:17)
at Object.<anonymous> (src/providers/settings/settings:12:21)
at Object.<anonymous> (src/unit-tests/settings.test.ts:2:18)
How do I configure my test script so that Jest recognises the modules that are initialised in the SettingsProvider constructor?
I'm relatively new to unit testing and have been trying to figure out how to accomplish this (but, as you can tell, without much joy so far!)
I appreciate any guidance/advice any developers out there might have on the matter.
Thanks!
I haven't used Jest yet - I usually work with jasmine/karma, the default from the #angular team - but can't you use the Testbed to configure it?
With karma/jasmine, you can do something like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
Http,
InAppBrowser
],
})
}));
beforeEach(async(() => TestUtils.beforeEachCompiler([]).then(compiled => {
fixture = compiled.fixture;
instance = compiled.instance;
fixture.autoDetectChanges(true);
})));