React testing library fire beforeinstallprompt event - javascript

I have a React component that listen to beforeinstallprompt event to handle PWA app installation. It has an effect hook like this:
useEffect(() => {
window.addEventListener('beforeinstallprompt', handleBeforeInstallPromptEvent);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPromptEvent);
};
}, [handleBeforeInstallPromptEvent]);
The handler handleBeforeInstallPromptEvent do some logic to show a banner asking the user to install the application if it has not already been installed.
To test this behaviour I created this jest test but it is not calling the event handler and consequently not showing the alert.
Am I missing something?
it('should render install app alert if not yet installed', async () => {
const event = createEvent('beforeinstallprompt', window, {
userChoice: new Promise((res) => res({ outcome: 'accepted', platform: '' })),
prompt: () => new Promise((res) => res(undefined)),
});
render(<InstallApp />);
await act(async () => {
fireEvent(window, event);
});
expect(screen.getByText(/Install app!/g)).toBeVisible();
});

I found the problem. The describe test block has this setup/teardown configuration that is preventing the events to be caught
beforeAll(() => {
window.addEventListener = jest.fn();
window.removeEventListener = jest.fn();
});
afterAll(() => {
(window.addEventListener as any).mockClear();
(window.removeEventListener as any).mockClear();
});

Related

react testing fire event not triggering the window,onpopstate event

I have a code which will execute whenever we navigate thro bowser back/forward arrow click event. I need to test this event listener using the react testing library.
const useWindowPop = (switchOrg) => {
useEffect(() => {
window.onpopstate = (e) => {
if (isLoggedIn) {
const queryOrg = queryString.parse(window.location.search);
if (queryOrg?.org) {
switchOrg(queryOrg?.org);
}
}
};
}, []);
};
when I am trying to test this snippet using react-testing library, I am not able to get this event listener executed. I am trying test this like below:
it("fires history event on Window", () => {
const switchOrg = jest.fn();
renderHook(() => useWindowPop(switchOrg), {
wrapper,
});
act(() => {
fireEvent.popState(window);
});
fireEvent(
window,
new window.PopStateEvent("popstate", {
location: "/track?org=123",
state: { page: 1 },
})
);
expect(switchOrg).toHaveBeenCalled();
});

Cypress - how to properly detect for JS errors in a page

I am in the process of writing a spec file with N it(s), where each it will visit a specific page of the application and will return the number of errors/warning in the applications.
I've posted also here: https://github.com/cypress-io/cypress/issues/4808
The solution offered does not seem to work:
it('should spy window.error', () => {
cy.visit('https://www.spiegel.de/', {
onBeforeLoad(win) {
cy.spy(win.console, 'error').as('spyWinConsoleError');
cy.spy(win.console, 'warn').as('spyWinConsoleWarn');
},
})
console.error('questo e errore')
cy.get('#spyWinConsoleError').should('be.calledOnce');
});
Any idea?
A couple of things to note,
there are two window objects in a Cypress test, the one the test runs in and the one the app runs in. Your call to console.error('questo e errore') is to the first, but your spy is on the second.
test code runs faster than Cypress commands, so console.error('questo e errore') is running before cy.visit() gets done.
These example all work,
Spying on runner window console
it('should spy on RUNNER window.error', () => {
cy.spy(window.console, 'error').as('spyWinConsoleError');
console.error('questo e errore')
cy.get('#spyWinConsoleError').should('be.calledOnce');
});
Spying on app window console
it('should spy on APP window.error', () => {
const win = cy.state('window')
cy.spy(win.console, 'error').as('spyWinConsoleError');
win.console.error('questo e errore')
cy.get('#spyWinConsoleError').should('be.calledOnce');
});
Catching an actual app error
it('should spy window.error', () => {
cy.visit('../app/spy-on-win-error.html', {
onBeforeLoad(win) {
cy.spy(win.console, 'error').as('spyWinConsoleError');
},
})
cy.get('#spyWinConsoleError').should('be.calledWith', 'from app');
});
<body>
<script>
setTimeout(() => {
console.error('from app')
}, 1000)
</script>
</body>
Waiting for spiegel.de to load
it('should spy window.error', () => {
cy.visit('https://www.spiegel.de/', {
onBeforeLoad(win) {
cy.spy(win.console, 'error').as('spyWinConsoleError');
win.console.error("questo รจ l'errore due"); // call here after spy is set
},
})
cy.get('#spyWinConsoleError')
.should('be.calledOnce');
});

Package not activating when running specs

I've created a package for Atom called quick-fold that jumps to the next foldable line and folds it on the command quick-fold:fold-next. I wanted to start getting into Atom specs so I can run tests on this package, however I've hit this problem where the package is just never activated when running the specs.
quick-fold-spec.js:
describe('QuickFold package', () => {
let editor, editorView;
beforeEach(async () => {
await atom.packages.activatePackage('language-javascript');
await atom.workspace.open('sample.js');
editor = atom.workspace.getActiveTextEditor();
editorView = atom.views.getView(editor);
});
describe('when the specs are run', () => {
it('opens the sample file', () => expect(!editor.isEmpty()).toBe(true));
// true
});
describe('when the quick-fold:fold-next event is triggered', () => {
beforeEach(async () => {
// Try to activate package by dispatching command:
atom.commands.dispatch(editorView, 'quick-fold:fold-next');
await atom.packages.activatePackage('quick-fold'); // Never resolves
});
it('activates the package', () => {
expect(atom.packages.isPackageActive('quick-fold')).toBe(true);
});
it('moves the cursor to a different line number', () => {
expect(editor.getCursorScreenPosition().row).not.toBe(0);
});
});
});
But atom.packages.activatePackage('quick-fold') never resolves. The package doesn't activate and instead it times out:
timeout: timed out after 5000 msec waiting for spec promise to resolve
The activation command is set in package.json:
"activationCommands": {
"atom-workspace": "quick-fold:fold-next"
},
so dispatching this should activate the package, and then the await atom.packages.activatePackage('quick-fold') should resolve. But the cursor position doesn't change and the package doesn't get activated.
(Note that atom.packages.activatePackage('quick-fold') is merely a promise - it doesn't activate the package but it resolves when the package gets activated.)
As is often the case, I figured it out in the end...
1. The beforeEach() function is missing a runs()
It should be
beforeEach(async () => {
await atom.packages.activatePackage('language-javascript');
await atom.workspace.open('sample.js');
runs(() => {
editor = atom.workspace.getActiveTextEditor();
editorView = atom.views.getView(editor);
return activationPromise = atom.packages.activatePackage('quick-fold');
});
});
where the runs() function returns a promise on activation of the package.
From the docs:
Specs are written by defining a set of blocks with calls to runs, which usually finish with an asynchronous call.
I'm too tired right now to understand exactly what that means but it rings true here.
2. The activation commands in package.json should be based on atom-text-editor
I.e., not atom-workspace
"activationCommands": {
"atom-text-editor": "quick-fold:fold-next"
},
This is probably because we have atom.commands.dispatch(editorView, 'quick-fold:fold-next') where the commands are dispatched to editorView = atom.views.getView(editor) instead of the Atom workspace.
Refactored - the "standard" way of doing it
describe('QuickFold package', () => {
let editor, editorView, activationPromise;
const foldNext = async (callback) => {
atom.commands.dispatch(editorView, 'quick-fold:fold-next');
await activationPromise;
return callback();
};
beforeEach(async () => {
await atom.packages.activatePackage('language-javascript');
await atom.workspace.open('sample.js');
runs(() => {
editor = atom.workspace.getActiveTextEditor();
editorView = atom.views.getView(editor);
return activationPromise = atom.packages.activatePackage('quick-fold');
});
});
describe('when the specs are run', () => {
it('opens the sample file', () => expect(!editor.isEmpty()).toBe(true));
});
describe('when the quick-fold:fold-next event is triggered', () => {
it('activates the package', () => {
return foldNext(() => expect(atom.packages.isPackageActive('quick-fold')).toBe(true));
});
it('moves the cursor to a different line number', () => {
return foldNext(() => expect(editor.getCursorScreenPosition().row).not.toBe(0));
});
});
});

How to verify console.log was called in componentDidMount using Jest and Enzyme?

I'm trying test for cases when my axios call does not get an HTTP response of 200. When axios does not get a successful response, it throws an error. I want to verify that console.log gets called twice in this case.
Here's a snippet of the class I'm testing:
class App extends React.Component {
...
async componentDidMount() {
let url = "/api/info/tmp"
try {
let response = await axios.get(url);
...do stuff
this.setState(...);
} catch (e) {
console.log("Could not get " + url);
console.log(e);
}
}
...
}
And here's a snippet of my jest test
let mockAxios = new MockAdapter(axios);
...
describe("App - componentDidMount() behavior test", () => {
beforeEach(() => {
app = shallow(<App />);
})
afterEach(() => {
app = undefined;
mockAxios.reset();
});
...
describe("Get " + url + " HTTP response status is not 200", () => {
beforeAll(() => {
mockAxios.onGet(url).reply(302, mockData);
});
it("Does not set state regardless of response body", () => {
console.log = jest.fn();
const state = app.state();
expect(console.log).toHaveBeenCalledTimes(2);
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
});
});
});
I know the console.log = jest.fn() bit is doing something because the console does not log the fake error anymore when I set it. However, the test fails because Expected mock function to have been called two times, but it was called zero times.
I've tried moving the console.log = jest.fn() into the "beforeEach", "beforeAll", and as a global variable.
UPDATE
I am pretty sure it's something to do with all the async that is going on.
If I do this:
it("Does not set state regardless of response body", async () => {
console.log = jest.fn();
await app.instance().componentDidMount();
expect(console.log).toHaveBeenCalledTimes(2);
const state = app.state();
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
});
Then the test still fails but my reason changed: Expected mock function to have been called two times, but it was called four times. Now I just got to figure out why it was called four times not twice.
UPDATE 2
I figured out why console.log was being called 4 times! Now I just need to figure out how I should refactor my tests.
If I comment out my jest mock, and even the whole unit test
it("Does not set state regardless of response body", async () => {
//const state = app.state();
//expect(state.solutions).toEqual({});
//expect(state.username).toEqual("");
//expect(console.log).toHaveBeenCalledTimes(2);
});
Then I can count in my console that there are already indeed two different console.log calls. shallow(<App />) must be already calling componentDidMount() or something. When I add app.instance().componentDidMount(), I can visually see that it is logging 4 times.
Updated Answer
Since it looks like you already know what you're doing with mocks, perhaps the issue has to do with componentDidMount().
I believe that your call to shallow(<App />) will already call App's componentDidMount() one time (which means your console.log will get called twice there).
Then, you subsequently call app.instance().componentDidMount() - that is, you call componentDidMount() again (which means your console.log will get called twice there again).
So, total... 4 calls to console.log.
Hope that points you in the right direction...
Original Answer
Actually, your question looks quite similar to [this StackOverFlow question on how to "How to mock console when it is used by a third-party library?"
You can use Jest mock functions to spyOn the global.console object.
For example, your test may look like this:
// Setup jest to spy on the console
const consoleSpy = jest.spyOn(global.console, 'log')
describe('App - componentDidMount() behavior test', () => {
beforeEach(() => {
jest.resetAllMocks() // reset your consoleSpy state back to initial
app = shallow(<App />)
})
...
it('Does not set state regardless of response body', () => {
const spy = jest.spyOn(global.console, 'log')
const state = app.state()
expect(consoleSpy).toHaveBeenCalledTimes(2)
expect(state.solutions).toEqual({})
expect(state.username).toEqual('')
})
...
Ideally, you'd move your API call outside of componentDidMount and into its own class method. Thay way it can be manually invoked from a lifecycle method or from an event callback. Also, you should anticipate the response to affect your UI state in some fashion (example: displaying a message to the user that the request failed and to try again).
The following example can be done with .then/.catch instead of async/await. Either way, you're working with Promises that are asynchronous and therefore they need asynchronous tests.
Note: The below assumes disableLifecycleMethods is true in the enzyme adapter. Also, just testing state changes (or a console.log) is a bit superfluous; instead, you would test if a component is rendered based upon the current state.
Working example: https://codesandbox.io/s/939w229l9r (includes both end to end and integration tests --- you can run the tests by clicking on the Tests tab located near the bottom left of the sandbox)
App.js (this will be a container that holds all relevant state and disperses it to its children as needed)
import React, { Component } from 'react';
class App extends Component {
state = = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
componentDidMount() {
this.fetchData("/api/info/tmp");
}
fetchData = async (url) => {
try {
const res = await axios.get(url);
...do stuff
this.setState({
error: "",
isLoading: false,
solutions: res.data.solutions,
username: res.data.username
});
} catch (err) {
this.setState({
error: err,
isLoading: false,
solutions: {},
username: ""
});
}
}
render() { ... }
}
App.test.js (this assumes you'd want an end to end test)
import { shallow } from 'enzyme';
import App from './App';
const timeout = () =>
new Promise(resolve => {
setTimeout(() => {
resolve();
}, 2000);
});
const initialState = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
describe("App", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<App />);
wrapper.setState({ ...initialState });
});
afterAll(() => {
wrapper.unmount();
});
it("sets data to state based upon successful API call", async () => {
wrapper.instance().fetchData("/api/info/tmp");
await timeout();
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({ somedata });
expect(wrapper.state('username')).toEqual("Some User");
});
it("displays an error upon unsuccessful API call", async () => {
wrapper.instance().fetchData("/api/bad/url");
await timeout();
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({});
expect(wrapper.state('username')).toEqual("");
expect(wrapper.state('error')).toEqual("No data found.");
});
});
App.test.js (this assumes you'd want an integration test)
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import React from "react";
import { shallow } from "enzyme";
import App from "../App";
const solutions = [{ ... }, { ... }];
const username = "Some User"
const mockAxios = new MockAdapter(axios);
const initialState = {
error: "",
isLoading: true,
solutions: {},
username: ""
};
describe("App", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<App />);
wrapper.setState({ ...initialState });
});
afterEach(() => {
mock.reset();
});
afterAll(() => {
mock.restore();
wrapper.unmount();
});
it("displays an error upon unsuccessful API call", async () => {
try {
mockAxios.onGet("/users").networkErrorOnce();
await axios.get("users");
} catch (err) {
const error = err.toString();
wrapper.setState({
error,
isLoading: false,
solutions: {},
username: ""
});
wrapper.update();
expect(wrapper.state('isLoading')).toBeEqual(error);
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual({});
expect(wrapper.state('username')).toEqual("");
}
});
it("sets data to state based upon successful API call", async () => {
try {
mockAxios.onGet("/users").reply(200, { solutions, username });
const res = await axios.get("users");
wrapper.setState({
error: "",
isLoading: true,
solutions: res.data.solutions,
username: res.data.username
});
wrapper.update();
expect(wrapper.state('isLoading')).toBeFalsy();
expect(wrapper.state('solutions')).toEqual(solutions);
expect(wrapper.state('username')).toEqual(username);
} catch (e) {
console.log(e);
}
});
});
I figured it out! Kind of... I am not certain why it works like this, but setting the mock in the actual "it" did not work.
The solution was making a beforeEach and afterEach
describe("Get " + url + " HTTP response status is not 200", () => {
beforeAll(() => {
mockAxios.onGet(url).reply(302, mockData);
});
beforeEach(() => {
console.log = jest.fn();
});
afterEach(() => {
jest.resetAllMocks();
});
it("Does not set state regardless of response body", async () => {
const state = app.state();
expect(state.solutions).toEqual({});
expect(state.username).toEqual("");
expect(console.log).toHaveBeenCalledTimes(2);
});
});

How to force Jest unit test assertion to execute before test ends?

I have the following test in my react native application, but the test should fail (because the action returned is not equal to the action I put in expectedActions. My guess is that it is passing because the expect test runs after the test has completed.
How can I force the test to wait until the promise is completed and the expect test runs? Is there another way of doing this?
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});
Ok, looks like I just missed some of the Jest docs - if you return the promise, i.e. return auth.authorize.mockImplementation(() => Promise.resolve... then Jest will wait until it's completed before continuing.
The are varios ways to test async code. Check the docs for examples: https://facebook.github.io/jest/docs/en/asynchronous.html
One could be returning the promise:
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
return authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});

Categories

Resources