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.
Related
I'm currently going through the process of upgrading from Jest 27 to 29.
I've noticed this in the 28 upgrade docs: https://jestjs.io/docs/28.x/upgrading-to-jest28#packagejson-exports.
Am I understanding correctly that if I'm testing under the jest-environment-jsdom environment, that I won't be able to import a library that doesn't export an ESM module? This seems to be the behaviour I'm observing but want to solidify my understanding.
From what I'm observing, the opposite is true. For example, their problem with uuid is that it only exports ESM (for browsers), but Jest needs CJS. I tested this with a local package:
package.json:
{
"private": true,
"devDependencies": {
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"my-pkg": "file:./my-pkg"
}
}
jest.config.js:
module.exports = {
testEnvironment: 'jsdom',
}
my-pkg/package.json:
{
"name": "my-pkg",
"version": "0.1.0",
"exports": {
".": {
"browser": {
"require": "./browser-require.js",
"import": "./browser-import.js"
}
}
}
}
my-pkg/browser-require.js:
module.exports = 'browser require'
my-pkg/browser-import.js:
export default 'browser import'
And finally, the test:
const myPkg = require('my-pkg')
test('test', () => {
expect(myPkg).toBe('browser require')
})
The test passes, meaning that Jest chose the CJS version. If I remove the CJS export from my-pkg, Jest fails with Cannot find module 'my-pkg'. I assume that Jest would use the ESM export only if you enable experimental ESM support. In case of uuid, they have a "default" export, which Jest will fail to parse as CJS because it's in ESM, resulting in the error they describe.
P.S. Even with Babel Jest would still use CJS, Babel just compiles ESM to CJS before running your tests. And only your source and test code, not dependencies, so it wouldn't compile my-pkg.
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
Background
I recently learned about CLASP and became excited about the possibility of using TDD to edit my Google Apps Scripts (GAS) locally.
NOTE: there might be a way to write tests using the existing GAS editor, but I'd prefer to use a modern editor if at all possible
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
I got farthest by using the gas-local package, and was able to mock a single dependency within a test
However I could not find a way to mock multiple dependencies in a single test/call, and so I created this issue
Challenge
Despite installing #types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax, respectively--see below for an illustration of this.
Related StackOverflow Post
Although there is a similar SO question on unit testing here, most of the content/comments appear to be from the pre-clasp era, and I was unable to arrive at a solution while following up the remaining leads. (Granted, it's very possible my untrained eye missed something!).
Attempts
Using gas-local
As I mentioned above, I created an issue (see link above) after trying to mock multiple dependencies while using gas-local. My configuration was similar to the jest.mock test I describe below, though it's worth noting the following differences:
I used ES5 syntax for the gas-local tests
My package configuration was probably slightly different
Using jest.mock
LedgerScripts.test.js
import { getSummaryHTML } from "./LedgerScripts.js";
import { SpreadsheetApp } from '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet';
test('test a thing', () => {
jest.mock('SpreadSheetApp', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { getActiveSpreadsheet: () => {} };
});
});
SpreadsheetApp.mockResolvedValue('TestSpreadSheetName');
const result = getSummaryHTML;
expect(result).toBeInstanceOf(String);
});
LedgerScripts.js
//Generates the summary of transactions for embedding in email
function getSummaryHTML(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = ss.getSheetByName("Dashboard");
// Do other stuff
return "<p>some HTML would go here</p>"
}
export default getSummaryHTML;
Result (after running jest command)
Cannot find module '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet' from 'src/LedgerScripts.test.js'
1 | import { getSummaryHTML } from "./LedgerScripts.js";
> 2 | import { SpreadsheetApp } from '../node_modules/#types/google-apps-script/google-apps-script.spreadsheet';
| ^
3 |
4 | test('test a thing', () => {
5 | jest.mock('SpreadSheetApp', () => {
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:307:11)
at Object.<anonymous> (src/LedgerScripts.test.js:2:1)
For reference, if I go to the google-apps-script.spreadsheet.d.ts file that has the types I want, I see the following declarations at the top of the file...
declare namespace GoogleAppsScript {
namespace Spreadsheet {
...and this one at the bottom of the file:
declare var SpreadsheetApp: GoogleAppsScript.Spreadsheet.SpreadsheetApp;
So maybe I am just importing SpreadsheetApp incorrectly?
Other files
jest.config.js
module.exports = {
clearMocks: true,
moduleFileExtensions: [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
testEnvironment: "node",
};
babel.config.js
module.exports = {
presets: ["#babel/preset-env"],
};
package.json
{
"name": "ledger-scripts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"dependencies": {
"#babel/core": "^7.11.1",
"#babel/preset-env": "^7.11.0",
"#types/google-apps-script": "^1.0.14",
"#types/node": "^14.0.27",
"babel-jest": "^26.3.0",
"commonjs": "0.0.1",
"eslint": "^7.6.0",
"eslint-plugin-jest": "^23.20.0",
"gas-local": "^1.3.1",
"requirejs": "^2.3.6"
},
"devDependencies": {
"#types/jasmine": "^3.5.12",
"#types/jest": "^26.0.9",
"jest": "^26.3.0"
}
}
Note: the scope of your question is broad and may require clarification.
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
You don't need Jest or any particular testing framework to mock the global Apps Script objects.
// LedgerScripts.test.js
import getSummaryHTML from "./LedgerScripts.js";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
console.log(typeof getSummaryHTML() === "string");
$ node LedgerScripts.test.js
true
So maybe I am just importing SpreadsheetApp incorrectly?
Yes, it is incorrect to import .d.ts into Jest.
Jest doesn't need the TypeScript file for SpreadsheetApp. You can omit it.
You only need to slightly modify the above example for Jest.
// LedgerScripts.test.js - Jest version
import getSummaryHTML from "./LedgerScripts";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
test("summary returns a string", () => {
expect(typeof getSummaryHTML()).toBe("string");
});
Despite installing #types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax
#types/google-apps-script does not contain modules and you do not import them. These are TypeScript declaration files. Your editor, if it supports TypeScript, will read those files in the background and suddenly you'll have the ability to get autocomplete, even in plain JavaScript files.
Additional comments
Here you check that a function returns a string, perhaps just to make your example very simple. However, it must be stressed that such testing is better left to TypeScript.
Since you returned an HTML string, I feel obligated to point out the excellent HTML Service and templating abilities of Apps Script.
Unit testing or integration testing? You mention unit testing, but relying upon globals is generally a sign you might not be unit testing. Consider refactoring your functions so they receive objects as input rather than calling them from the global scope.
Module syntax: if you use export default foo, you then import without curly braces: import foo from "foo.js" but if you use export function foo() { then you use the curly braces: import { foo } from "foo.js"
I am trying to develop an Alexa skill, that fetches information from a DynamoDB database. In order to use that I have to import the aws-sdk.
But for some reason when I import it, my skill stops working. The skill does not even open. My code is hosted from the Alexa Developer Console.
Here's what happens:
In the testing panel, when I input 'Open Cricket Update' (the app name), Alexa's response is, 'There was a problem with the requested skill's response'.
This happens only when I import the aws-sdk.
What am I doing wrong?
index.js
const Alexa = require('ask-sdk-core');
const AWS = require('aws-sdk');
AWS.config.update({region:'us-east-1'});
const table = 'CricketData';
const docClient = new AWS.DynamoDB.DocumentClient();
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Hello! Welcome to cricket update.';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
package.json
{
"name": "hello-world",
"version": "1.1.0",
"description": "alexa utility for quickly building skills",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.6.0",
"ask-sdk-model": "^1.18.0",
"aws-sdk": "^2.326.0"
}
}
You are missing the exports.handler block at the end of your index.js that "builds" the skill composed from your handlers, e.g.
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(LaunchRequestHandler)
.lambda();
A more complete example can be found here
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.