Mock 'element' in jest tests - jestjs

I have a helper which I use for Detox tests which hold abstractions of most commonly occurring actions. Like this.
/**
* Looks for a search input and inputs the query
*/
export const inputSearchQuery = async ({ query = '', placeholderText = '' }) => {
if (placeholderText) {
// look for search input
await expect(element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText)))).toBeVisible();
// tap search input
await element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText))).tap();
// type in query
await element(by.id(TestID.SEARCH_INPUT).withDescendant(by.text(placeholderText))).typeText(query);
} else {
// look for search input
await expect(element(by.id(TestID.SEARCH_INPUT))).toBeVisible();
// tap search input
await element(by.id(TestID.SEARCH_INPUT)).tap();
// type in query
await element(by.id(TestID.SEARCH_INPUT)).typeText(query);
}
};
I want this layer of abstraction to be tested with Jest. So I did the following,
describe('e2e helper', () => {
describe('inputSearchQuery()', () => {
test('should check whether the search input is visible', async () => {
const mockToBeVisible = jest.fn();
await inputSearchQuery({});
expect(mockToBeVisible).toBeCalledTimes(1);
});
});
});
And obviously got the error,
ReferenceError: element is not defined
I understand that element comes from the environment. How do I configure my Jest setup...
"jest": {
"preset": "react-native",
"snapshotSerializers": [
"./node_modules/enzyme-to-json/serializer"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-navigation)"
],
"moduleNameMapper": {
"package.json": "<rootDir>/__mocks__/package.json"
},
"testPathIgnorePatterns": [
"/e2e/"
],
// I'm guessing something like this but doesn't work out of the box
"testEnvironment": "detox"
},
...to address this?

Related

Error: Schematic "rest" cannot resolve the factory

function looks like this:
I am using first time nestjs for generators, it seems like i have some issue in collection.json
export function application(options: any) {
// eslint-disable-next-line #typescript-eslint/no-unused-vars
return (_tree: Tree, _context: SchematicContext) => {
return chain([
externalSchematic('#nestjs/schematics', 'application', options),
generate(options),
]);
};
}
export function generate(_options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
return chain([
addDependencies(tree, _options),
renderTemplates(_options),
modifyGeneratedFiles(_options),
])(tree, context);
};
}
collection.json file looks like this.
"schematics": {
"resolver": {
"description": "Nodejs GraphQL project scaffold",
"factory": "./resolver-generator/index#application",
"schema": "./resolver-generator/resolver-schema.json"
},
"rest": {
"description": "Nodejs Rest generator project scaffold",
"factory": "./rest-generator/index#applicationRest",
"schema": "./rest-generator/rest-schema.json"
}
Thank you for help i am able to solve it, it was happening bcoz of wrong function name when we use nestjs/schematics we have to pass application not applictionRest

Chrome Extension V3 Set Icon [duplicate]

I'm trying to develop a simple chrome extension. There is a pageAction's default icon that should appear on the pages with a specific URL (http://www.example.com/*).
There is a two file
manifest.json
{
"manifest_version": 2,
"name": "name",
"description": "description",
"version": "1.0",
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"page_action": {
"default_icon" : "images/icons/19.png"
},
"permissions": [
"declarativeContent"
]
}
background.js
chrome.runtime.onInstalled.addListener(function () {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
chrome.declarativeContent.onPageChanged.addRules([
{
// rule1
conditions : [
new chrome.declarativeContent.PageStateMatcher({
pageUrl : {urlPrefix : 'http://www.example.com/'}
})
],
actions : [
new chrome.declarativeContent.ShowPageAction()
]
},
{
// rule2
conditions : [
new chrome.declarativeContent.PageStateMatcher({
pageUrl : {queryContains : 'q1=green'}
})
],
actions : [
new chrome.declarativeContent.SetIcon({
path : {"19" : "images/icons/green.png"}
})
]
}
]);
});
});
rule1 should show pageAction's icon and rule2 should change icon to green version on the pages with URL that looks like http://www.example.com/?q1=green
But during installation of extension things come to:
Error in response to events.removeRules: Error: Invalid value for argument 1. Property '.0': Value does not match any valid type choices.
I dug deeply into this error, and it seems like the documentation does not reflect well the fact that using path parameter is not implemented. This is certainly a bug, tracked here.
For now, to fix this you need to load the image and convert it to ImageData format before calling SetIcon.
// Takes a local path to intended 19x19 icon
// and passes a correct SetIcon action to the callback
function createSetIconAction(path, callback) {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var image = new Image();
image.onload = function() {
ctx.drawImage(image,0,0,19,19);
var imageData = ctx.getImageData(0,0,19,19);
var action = new chrome.declarativeContent.SetIcon({imageData: imageData});
callback(action);
}
image.src = chrome.runtime.getURL(path);
}
chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
createSetIconAction("images/icons/green.png", function(setIconAction) {
chrome.declarativeContent.onPageChanged.addRules([
/* rule1, */
{
conditions : [
new chrome.declarativeContent.PageStateMatcher({
pageUrl : {queryContains : 'q1=green'}
})
],
actions : [ setIconAction ]
}
]);
});
});
If needed, this can be generalized to support high-DPI icon (19 + 38):
function createSetIconAction(path19, path38, callback) {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var image19 = new Image();
image19.onload = function() {
ctx.drawImage(image19,0,0,19,19); // fixed
var imageData19 = ctx.getImageData(0,0,19,19);
var image38 = new Image();
image38.onload = function() {
ctx.drawImage(image38,0,0,38,38);
var imageData38 = ctx.getImageData(0,0,38,38);
var action = new chrome.declarativeContent.SetIcon({
imageData: {19: imageData19, 38: imageData38}
});
callback(action);
}
image38.src = chrome.runtime.getURL(path38);
}
image19.src = chrome.runtime.getURL(path19);
}
In fact, you can use new chrome.declarativeContent.SetIcon({ path:'yourPath.png' }),
No need to specify size path: {"19": "images/icons/green.png"}, its default value is: 16
Use declarativeContent.SetIcon need to pay attention to a problem, it is actually a bug.
Actual use of path will eventually be automatically converted to ImageData.
see screenshot:
The root cause of the error of declarativeContent.SetIcon is: it is an asynchronous API, but at the same time it has no asynchronous callback. The only thing you can do is wait.
const action = new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' });
console.log(action.imageData); // => undefined
see screenshot:
// invalid
new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' }, action => console.log(action));
It takes a while to wait:
const action = new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' });
setTimeout(() => {
console.log(action.imageData); // {16: ArrayBuffer(1060)}
}, 5);
see screenshot:
When you understand the reason for the error of SetIcon, the problem will be solved well.
You only need to put the operation of addRules in the event.
onInstalled event
const rule2 = { id: 'hideAction', conditions: [...], actions: [new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' })]};
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
chrome.declarativeContent.onPageChanged.addRules([rule2]);
});
});
pageAction.onClicked
const rule2 = { id: 'hideAction', conditions: [...], actions: [new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' })]};
chrome.pageAction.onClicked.addListener(() => {
if (condition) {
chrome.declarativeContent.onPageChanged.removeRules(['hideAction']);
} else {
chrome.declarativeContent.onPageChanged.addRules([rule2]);
}
});
There are some related information:
SetIcon source code
declarativeContent.SetIcon = function (parameters) {
// TODO(devlin): This is very, very wrong. setIcon() is potentially
// asynchronous (in the case of a path being specified), which means this
// becomes an "asynchronous constructor". Errors can be thrown *after* the
// `new declarativeContent.SetIcon(...)` call, and in the async cases,
// this wouldn't work when we immediately add the action via an API call
// (e.g.,
// chrome.declarativeContent.onPageChange.addRules(
// [{conditions: ..., actions: [ new SetIcon(...) ]}]);
// ). Some of this is tracked in http://crbug.com/415315.
setIcon(
parameters,
$Function.bind(function (data) {
// Fake calling the original function as a constructor.
$Object.setPrototypeOf(this, nativeSetIcon.prototype);
$Function.apply(nativeSetIcon, this, [data]);
}, this)
);
};
Discussion of related issues:
http://crbug.com/415315
No solution
As the guys before me mentioned, this is a bug. There are no solutions, only workarounds.
Workarounds
#1: Draw icon using canvas
As Xan described in his answer already.
#2 Wait for icon load (timeout hack)
Thanks to weiya-ou's answer I realized that I can just wait for the async icon data transformation to finish.
// Make your handler `async`
chrome.runtime.onInstalled.addListener(async () => {
const action = await new chrome.declarativeContent.SetIcon({
path: {
19: 'images/19.png',
38: 'images/38.png',
},
})
// THE WAIT STARTS
// Wait max. 10 loops
for (let i = 0; i < 10; i++) {
// Create a promise
const checkAvailability = new Promise((resolve) => {
// Resolve promise after 100ms
setTimeout(() => resolve(!!action.imageData), 100)
})
// Wait for the promise resolution
const isAvailable = await checkAvailability
// When image available, we are done here
if (isAvailable) break
}
// THE WAIT ENDS
const condition = new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostEquals: 'my.page.net' },
})
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [condition],
actions: [action],
},
]);
});
});
#3 Use chrome.tabs
You would need the tabs permission (as said here).
chrome.tabs.onUpdated.addListener((tabId, { status }, { url }) => {
// Only check when URL is resolved
if (status !== 'complete') return
// What is our target page?
const isOurPage = url?.match(/my\.page\.net/)
if (isOurPage) {
// Show active icon
chrome.pageAction.setIcon({
path: {
19: 'images/19.png',
38: 'images/38.png',
},
tabId,
})
} else {
// Show inactive icon
chrome.pageAction.setIcon({
path: {
19: 'images/19-inactive.png',
38: 'images/38-inactive.png',
},
tabId,
})
}
})

Mocking custom Hook with jest and enzyme results in 'xxx is not a function or its return value is not iterable' Error

I am very new to jest and enzyme. In my Project I will use a SPA React based Application. Containing a Context Provider for the data, also several hooks. I Using now Jest (with ts-jest and enzyme)
My jest.config looks like this
module.exports = {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"snapshotSerializers": ["enzyme-to-json/serializer"]
So my first stept so test UI components works.
Next step was to test componentes with mocked data. But there I got the error described at the bottom.
I have a functional component like this:
export default function CurrentWeather(props: ICurrentWeatherProps) {
const [data, Reload] = useCurrentWeather(props.locationID);
return (<div>......</div>)
}
You will notice the useCurrentWeather hook, here is the code for this:
import { useEffect, useState } from 'react';
import { useLocationState } from '../context/locationContext';
import { ILocationData } from './useLocations';
import _ from 'lodash';
...
export default function useCurrentWeater(locationId: number) {
const locationState = useLocationState();
const Reload = () => { GetData() }
const [Data, SetData] = useState<IWeatherDataInfo>({Id:0,ConditionIcon:'',Date:new Date(),MaxTemp:0, MinTemp:0});
async function GetData() { .... }
useEffect(Reload, [locationState.data, locationId]);
return [Data, Reload] as const;
}
Now I wand to mock these Hook. I tried following
import React from 'react';
import { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CurrentWeather from '../components/Weather/CurrentWeather';
import { IWeatherDataInfo } from '../Hooks/useWeaters';
configure({ adapter: new Adapter() });
const mockWeatherReload = jest.fn();
const mockdata: IWeatherDataInfo = { Date: new Date(), ConditionIcon: "", MinTemp: 0, MaxTemp: 10 };
jest.mock('../Hooks/useCurrentWeather', () => ({
useCurrentWeather: jest.fn(()=>{ [mockdata, mockWeatherReload]})
}));
describe("WeatherWidget", () => {
it("RenderOnlyAddButton", () => {
const container = shallow(<CurrentWeather locationID={1} hidden={false} />);
});
});
Now, when I execute this test, I will get this error result:
src/tests/WeatherWidget.test.tsx
● WeatherWidget › RenderOnlyAddButton
TypeError: (0 , _useCurrentWeather.default) is not a function or its return value is not iterable
9 |
10 | export default function CurrentWeather(props: ICurrentWeatherProps) {
> 11 | const [data, Reload] = useCurrentWeather(props.locationID);
| ^
12 | return (
What I'm doing wrong here? Is there what I'm missing?
Try like this:(below should be your functional component's test file)
const mockUseCurrentWeather = jest.fn();
jest.mock("put here the absolute path", () => ({
__esModule: true,
useCurrentWeather: (...args: any) => mockUseCurrentWeather(...args),
}));
describe("WeatherWidget", () => {
beforeEach(() => {
mockUseCurrentWeather.mockClear();
mockUseCurrentWeather.mockReturnValue([undefined, undefined]);
});
it("RenderOnlyAddButton", () => {
mockUseCurrentWeather.mockClear();
mockUseCurrentWeather.mockReturnValue([undefined, undefined]);
const container = shallow(<CurrentWeather locationID={1} hidden={false} />);
});
});

Jest with NestJS and async function

I'm trying to a test a async function of a service in nestJS.
this function is async... basically get a value (JSON) from database (using repository - TypeORM), and when successfully get the data, "transform" to a different class (DTO)...
the implementation:
async getAppConfig(): Promise<ConfigAppDto> {
return this.configRepository.findOne({
key: Equal("APPLICATION"),
}).then(config => {
if (config == null) {
return new class implements ConfigAppDto {
clientId = '';
clientSecret = '';
};
}
return JSON.parse(config.value) as ConfigAppDto;
});
}
using a controller, I checked that this worked ok.
Now, I'm trying to use Jest to do the tests, but with no success...
My problem is how to mock the findOne function from repository..
Edit: I'm trying to use #golevelup/nestjs-testing to mock Repository!
I already mocked the repository, but for some reason, the resolve is never called..
describe('getAppConfig', () => {
const repo = createMock<Repository<Config>>();
beforeEach(async () => {
await Test.createTestingModule({
providers: [
ConfigService,
{
provide: getRepositoryToken(Config),
useValue: repo,
}
],
}).compile();
});
it('should return ConfigApp parameters', async () => {
const mockedConfig = new Config('APPLICATION', '{"clientId": "foo","clientSecret": "bar"}');
repo.findOne.mockResolvedValue(mockedConfig);
expect(await repo.findOne()).toEqual(mockedConfig); // ok
const expectedReturn = new class implements ConfigAppDto {
clientId = 'foo';
clientSecret = 'bar';
};
expect(await service.getAppConfig()).toEqual(expectedReturn);
// jest documentation about async -> https://jestjs.io/docs/en/asynchronous
// return expect(service.getAppConfig()).resolves.toBe(expectedReturn);
});
})
the expect(await repo.findOne()).toEqual(mockedConfig); works great;
expect(await service.getAppConfig()).toEqual(expectedReturn); got a timeout => Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout;
using debug, I see that the service.getAppConfig() is called, the repository.findOne() too, but the .then of repository of findOne is never called.
Update: I'm trying to mock the repository using #golevelup/nestjs-testing, and for some reason, the mocked result don't works on service.
If I mock the repository using only jest (like code below), the test works... so, I think my real problem it's #golevelup/nestjs-testing.
...
provide: getRepositoryToken(Config),
useValue: {
find: jest.fn().mockResolvedValue([new Config()])
},
...
So, my real problem is how I'm mocking the Repository on NestJS.
For some reason, when I mock using the #golevelup/nestjs-testing, weird things happens!
I really don't found a good documentation about this on #golevelup/nestjs-testing, so, I gave up using it.
My solution for the question was to use only Jest and NestJS functions... the result code was:
Service:
// i'm injecting Connection because I need for some transactions later;
constructor(#InjectRepository(Config) private readonly configRepo: Repository<Config>, private connection: Connection) {}
async getAppConfig(): Promise<ConfigApp> {
return this.configRepo.findOne({
key: Equal("APPLICATION"),
}).then(config => {
if (config == null) {
return new ConfigApp();
}
return JSON.parse(config.value) as ConfigApp;
})
}
Test:
describe('getAppConfig', () => {
const configApi = new Config();
configApi.key = 'APPLICATION';
configApi.value = '{"clientId": "foo", "clientSecret": "bar"}';
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
ConfigAppService,
{
provide: getRepositoryToken(Config),
useValue: {
findOne: jest.fn().mockResolvedValue(new
Config("APPLICATION", '{"clientId": "foo", "clientSecret": "bar"}')),
},
},
{
provide: getConnectionToken(),
useValue: {},
}
],
}).compile();
service = module.get<ConfigAppService>(ConfigAppService);
});
it('should return ConfigApp parameters', async () => {
const expectedValue: ConfigApp = new ConfigApp("foo", "bar");
return service.getAppConfig().then(value => {
expect(value).toEqual(expectedValue);
})
});
})
some sources utilized for this solution:
https://github.com/jmcdo29/testing-nestjs/tree/master/apps/typeorm-sample
I think expect(await repo.findOne()).toEqual(mockedConfig); works because you mocked it, so it returns right away.
In the case of expect(await service.getAppConfig()).toEqual(expectedReturn);, you did not mock it so it is probably taking more time, thus the it function returns before the Promise resolved completely.
The comments you posted from jest documentation should do the trick if you mock the call to getAppConfig().
service.getAppConfig = jest.fn(() => Promise.resolve(someFakeValue))
or
spyOn(service, 'getAppConfig').and.mockReturnValue(Promise.resolve(fakeValue))
This answer from #roberto-correia made me wonder if there must be something wrong with the way we are using createMock from the package #golevelup/nestjs-testing.
It turns out that the reason why the method exceeds the execution time has to do with the fact that createMock does not implement the mocking, and does not return anything, unless told to do so.
To make the method work, we have to make the mocked methods resolve something at the beginning of the test:
usersRepository.findOneOrFail.mockResolvedValue({ userId: 1, email: "some-random-email#email.com" });
A basic working solution:
describe("UsersService", () => {
let usersService: UsersService;
const usersRepository = createMock<Repository<User>>();
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: usersRepository,
},
}).compile();
usersService = module.get(UsersService);
});
it("should be defined", () => {
expect(usersService).toBeDefined();
});
it("finds a user", async () => {
usersRepository.findOne.mockResolvedValue({ userId: 1, email: "some-random-email#email.com" });
expect(await usersRepository.findOne()).toBe({ userId: 1, email: "some-random-email#email.com" });
});
});

How to validate graphql schema with fragmented documents

i was thinking it would be cool to support schema validation at the unit test level so we could be aware of breaking changes to queries when we upgrade our api
i’d like to set up the test so that it supports auto-discovery of any new *.graphql files but in doing so, the jest process thinks the current working directory is in __tests__ so when i evaluate the graphql document manually with the loader, relative fragments in queries like this fail:
#import "./fragments/FullUserData.graphql"
query User(
$zid: String!
) {
user {
userData: get(
zid: $zid
) {
...FullUserData
}
}
}
failure message:
Error: Cannot find module './fragments/FullUserData.graphql' from 'schemaValidation-test.js'"
if i move fragments folder into the __tests__ dir, the test gets happy.
any ideas on what I can do to trick the evaluation to process the fragment as if I was relative to the fragment directory?
__tests__/
- schemaValidation-test.js
queries/
- someQuery.graphql
- fragments/someFragment.graphql
i tried process.chdir() to the queries dir from within jest but no dice
here is the validator:
// __tests__/schemaValidation-test.js
import glob from 'glob'
import { validate } from 'graphql/validation'
import loader from 'graphql-tag/loader'
import schema from 'api/lib/app/graphql/schema'
import path from 'path'
import fs from 'fs'
const gqlDir = path.join(__dirname, '..')
const queryDir = path.join(gqlDir, 'queries', 'shared')
const pattern = `${queryDir}/!(fragments)*.graphql`
const getGraphqlFiles = () => glob.sync(pattern)
describe('api schema', () => {
const files = getGraphqlFiles()
for(var file of files) {
const buffer = fs.readFileSync(file)
let document = (buffer || "").toString()
try {
document = eval(loader.call(
{ cacheable: () => ({}) },
document
))
} catch (e) {
fail(`could not parse ${file}, ${e}`)
}
it(`${file} passes validation`, () => {
const errors = validate(
schema,
document,
)
expect(errors).toEqual([])
})
}
})
How can I tell the loader I am in a different directory relative to the fragment?
I figured this out. The key was to use require instead of fs.readFileSync
import glob from 'glob'
import { validate } from 'graphql/validation'
import schema from 'api/lib/app/graphql/schema'
import path from 'path'
const gqlDir = path.join(__dirname, '..')
const queryDir = path.join(gqlDir, 'queries', 'shared')
const pattern = `${queryDir}/!(fragments)*.graphql`
const getGraphqlFiles = () => glob.sync(pattern)
describe('rent-js-api schema', () => {
const files = getGraphqlFiles()
files.forEach(file => {
/* eslint-disable import/no-dynamic-require */
const document = require(file)
it(`${file} passes validation`, () => {
const errors = validate(
schema,
document,
)
expect(errors).toEqual([])
})
})
})
here is jest.config.json
{
"setupFiles": [
"<rootDir>/test/jest/shim.js",
"<rootDir>/test/jest/setup.js"
],
"moduleDirectories": ["node_modules", "src", "test/jest", "test"],
"collectCoverage": false,
"testMatch": ["**/*-test.js"],
"collectCoverageFrom": [
"**/src/**/*.{js,ts,jsx,tsx}",
"!**/src/**/*-test.js",
"!**/index.{ts,js}",
"!**/src/**/const.{ts,js}",
"!**/ui/theme/**",
"!**/src/**/*.d.{ts,tsx}",
"!**/node_modules/**",
"!**/src/ui/*/themes/**"
],
"coverageDirectory": "./coverage",
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/test/jest/noop-styles",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/jest/noop-binary",
"^.+\\.html$": "<rootDir>/test/jest/htmlLoader"
},
"moduleFileExtensions": [
"graphql",
"js",
"json",
"ts",
"tsx"
],
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.tsx?$": "babel-jest",
"^.+\\.graphql$": "jest-transform-graphql"
},
"testPathIgnorePatterns": [
"<rootDir>/node_modules/",
"^.*__tests__/__helpers__.*"
],
"snapshotSerializers": [
"enzyme-to-json/serializer",
"jest-serializer-html"
]
}

Resources