Updating window object per test - javascript

I have some conditional logic in a component that reads window.location.pathname.
To test this I want to shallow render a component without touching the window, asserting, then shallow rendering again but this time with a different window.location.pathname and assert.
It seems like after the first shallow render enzyme locks the window object for all following renders. Ignoring any changes I make to the window object in the current process.
describe('SomeComponent', () => {
describe('render', () => {
it('default render', () => {
const component = <SomeComponent><div>test</div></SomeComponent>;
const wrapper = shallow(component);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
describe('lifecycle', () => {
describe('componentDidMount', () => {
it('calls Foo.bar with the locations pathname', () => {
const pathname = '/test';
Object.defineProperty(window.location, 'pathname', {
writable: true,
value: pathname,
});
const wrapper = shallow(<SomeComponent />);
expect(Foo.bar.mock.calls[0][0]).toBe(pathname);
});
});
});
});
So the second test works when run by itself. But when that first test runs then the second test fails. Apparently enzyme doesn't let you update the window between test.
Is this true or am I missing something. If it is true then what would be an alternative to test this?
Thanks

Related

In Enzyme, how to get a function from the props of a functional component?

I am writing unit tests for my react project using Jest and Enzyme.
As shown below, I passed a function named updateUser to the to-be-tested component EditCard via props.
describe('The EditCard screen', () => {
let wrapper;
beforeEach(() => {
const defaultProps: Partial<EditCardProps> = {
toggleEditing: jest.fn(),
user: mockUsers[0],
updateUser: jest.fn(), // passes this function to the "EditCard" component via props
showSnackbar: jest.fn(),
};
wrapper = shallow(<EditCard {...(defaultProps as EditCardProps)} />);
});
Then I want to test how many times it was called after simulating a click on a button.
it('should match the snapshot when the "Name" textfield is not filled and the "submit" button is clicked', () => {
wrapper.find('#Name').simulate('change', { target: { value: null } });
wrapper.find('#submit').simulate('click');
// Try to get the "updateUser" function from the props, but get "undefined".
expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
});
But I got the error shown below:
Matcher error: received value must be a mock or spy function
Received has value: undefined
24 | wrapper.find('#Name').simulate('change', { target: { value: null } });
25 | wrapper.find('#submit').simulate('click');
> 26 | expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
Could someone tell me where I did wrong? Why I cannot get the function from the props and undefined was returned?
Thanks in advance!
A few tweaks to your code should have it working...
import * as React from "react";
import { mount, ReactWrapper } from "enzyme";
import EditCard from "../path/to/EditCard";
/*
I'd recommend defining jest fns here to make them easier to reference anywhere
within the tests below; otherwise, it'll have to referenced via
'defaultProps.updateUser', 'defaultProps.showSnackbar', ...etc.
Using 'const' here allows us to define these variables within the
module's closure -- in short, only accessible within these tests and NOT
globally accessible (from other file tests).
*/
const showSnackbar = jest.fn();
const toggleEditing = jest.fn();
const updateUser = jest.fn();
/*
if the EditCard component is properly typed, then you shouldn't need to
add types to this 'defaultProps' object
*/
const defaultProps = {
showSnackbar,
toggleEditing,
updateUser,
user: mockUsers[0]
};
describe('The EditCard screen', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
/*
I'd recommend mount over shallow because child components can be
deeply nested and require multiple .dive calls; however, if
you know the child components of "EditCard" are just simple JSX elements,
then shallow will be fine
*/
wrapper = mount(<EditCard {...defaultProps} />);
});
it("should not call 'updateUser' when the form is submitted with an empty '#Name' field", () => {
/*
I'm not sure what simulating "null" does for this input, but assuming this
input is required you should at least pass a string value -- assuming
"#Name" input is of type 'text' | 'password' | 'email' => string and
not a number. On a related note, if it's required, then simply remove this
code as it doesn't do much for the test.
*/
// wrapper.find('#Name').simulate('change', { target: { value: "" } });
/*
I'm assuming this then simulates a form submit. Unfortunately,
pressing the submit button won't work. Instead you'll have to
simulate a form submit. This is a limitation of Enzyme and last I
checked hasn't been/can't be fixed.
*/
wrapper.find('form').simulate('submit');
/*
it should then NOT call the jest.fn() "updateUser" when submitted since
'#Name' is empty. Notice that we're referencing 'updateUser' -- the jest fn
defined above -- and not the wrapper.prop fn.
*/
expect(updateUser).not.toHaveBeenCalled();
});
// include other tests...
});
Here's a working example (click the Tests tab to run tests):

Jest: Testing window.location.reload

How do I write a test that makes sure that the method reloadFn does in fact reload the window? I found this resource but I am unclear on how to expect a window reload when writing a test when that window reload happens in a given function. Thanks for the help!
const reloadFn = () => {
window.location.reload(true);
}
Updated Answer (November 2021)
Package:
"jest": "^26.6.0"
"#testing-library/jest-dom": "^5.11.4"
Build: create-react-app 4
describe("test window location's reload function", () => {
const original = window.location;
const reloadFn = () => {
window.location.reload(true);
};
beforeAll(() => {
Object.defineProperty(window, 'location', {
configurable: true,
value: { reload: jest.fn() },
});
});
afterAll(() => {
Object.defineProperty(window, 'location', { configurable: true, value: original });
});
it('mocks reload function', () => {
expect(jest.isMockFunction(window.location.reload)).toBe(true);
});
it('calls reload function', () => {
reloadFn(); // as defined above..
expect(window.location.reload).toHaveBeenCalled();
});
});
Note: Updated answer because the the old answer wasn't supported with latest jest version used in CRA.
Old answer
Here’s the solution but refactored for better organization:
describe('test window location\'s reload function', () => {
const { reload } = window.location;
beforeAll(() => {
Object.defineProperty(window.location, 'reload', {
configurable: true,
});
window.location.reload = jest.fn();
});
afterAll(() => {
window.location.reload = reload;
});
it('mocks reload function', () => {
expect(jest.isMockFunction(window.location.reload)).toBe(true);
});
it('calls reload function', () => {
reloadFn(); // as defined above..
expect(window.location.reload).toHaveBeenCalled();
});
});
Thanks :)
If you use TypeScript with Jest:
Idea
Create a copy and then delete window's location property.
Now set location property with reload function mocked.
Set the original value back when test completes.
Code: TypeScript 3.x and below
const location: Location = window.location;
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
// <code to test>
// <code to test>
// <code to test>
expect(window.location.reload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
window.location = location;
Code: TypeScript 4+
TypeScript 4 has stricter checks (which is a good thing), so I am not really sure if there's a way to do this other than to suppress the error using #ts-ignore or #ts-expect-error.
WARNING: Suppressing TypeScript validations can be 🔴dangerous🔴.
const location: Location = window.location;
// WARNING:
// #ts-ignore and #ts-expect-error suppress TypeScript validations by ignoring errors.
// Suppressing TypeScript validations can be 🔴dangerous🔴.
// #ts-ignore
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
// <code to test>
// <code to test>
// <code to test>
expect(window.location.reload).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
window.location = location;
You can also simplify Murtaza Hussain's solution to
describe('refreshPage', () => {
const { reload } = window.location;
beforeAll(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: { reload: jest.fn() },
});
});
afterAll(() => {
window.location.reload = reload;
});
it('reloads the window', () => {
refreshPage();
expect(window.location.reload).toHaveBeenCalled();
});
});
You could use sessionStorage to save a value for each reload.
As long as the browser does not close, the value will remain in sessionStorage.
When the page reloads the value will increment. Verify the fresh reload with this value.
Test this by pasting reloadFn() into the console.
The console will display Reload count: 1, and increment with each reload.
const reloadFn = () => {
window.location.reload(true);
}
window.onload = function() {
// get reloadCount from sessionStorage
reloadCount = sessionStorage.getItem('reloadCount');
// reloadCount will be null the first page load or a new value for each reload
if (reloadCount) {
// increment reloadCount
reloadCount = parseInt(reloadCount) + 1;
// save the new value to sessionStorage
sessionStorage.setItem('reloadCount', reloadCount);
console.log("Reload count: " + reloadCount);
} else {
// if reloadCount was null then set it to 1 and save to sessionStorage
sessionStorage.setItem('reloadCount', 1);
console.log("Page was loaded for the first time");
}
}
Inside your function testing reloadFn, you should use the mock code you linked to:
Object.defineProperty(window.location, 'reload', {
configurable: true,
}); // makes window.location.reload writable
window.location.reload = jest.fn(); // set up the mock
reloadFn(); // this should call your mock defined above
expect(window.location.reload).toHaveBeenCalled(); // assert the call
window.location.reload.mockRestore(); // restore window.location.reload to its original function
For a more improved test, you can use
expect(window.location.reload).toHaveBeenCalledWith(true);
Of note is that this is not actually verifying that the window is reloaded which is outside of the scope of a unit test. Something like browser testing or integration testing would verify that.
Instead of using workarounds with Object.defineProperty you can use native Jest's spyOn function in the following way:
test("reload test", () => {
const { getByText } = renderComponentWithReloadButton()
const reload = jest.fn()
jest
.spyOn(window, "location", "get")
.mockImplementation(() => ({ reload } as unknown as Location))
// Call an action that should trigger window.location.reload() function
act(() => {
getByText("Reload me").click()
})
// Test if `reload` function was really called
expect(reload).toBeCalled()
})
Also make sure you clear the mock after test by using for example jest.clearAllMocks() function.
If someone is looking this up in 2020, well i had the same issued.
Why is the problem happening for some and not for others?
Well it all depends on the version of chrome you are running, i wrote a test for a component that eventually called window.location.reload. Below is the section of the component code:
onConfirmChange() {
const {data, id} = this.state;
this.setState({showConfirmationModal: false}, () => {
this.update(data, id)
.then(() => window.location.reload());
});
}
The test initially failed on my build server with chrome version 71, it was passing for my local with chrome version 79.
I updated my chrome today to version 84, and the problem popped up in my local
The deleting of window.local seems to be un supported. Tried all the solution i could find in google nothing worked.
What is the solution then?
It was actually very simple, for react testing my system is using enzyme so what i did was wrapped the window.location.reload in an instance method and stubbed that in the test
JSX code:
reloadWindow() {
window.location.reload();
}
onConfirmChange() {
const {data, id} = this.state;
this.setState({showConfirmationModal: false}, () => {
this.update(data, id)
.then(() => reloadWindow());
});
}
TEST
it('check what happened', () => {
render();
const component = wrapper.instance();
sandbox.stub(component, 'reloadWindow').callsFake();
});
Updated Answer (May 2021)
I ran into some issues with a lot of the answers in the thread. I think version changes over time to the underlying libraries caused the breakage.
My Config:
"typescript": "~4.1.5"
"jest": "^26.6.3"
"jest-environment-jsdom": "^26.6.2"
Also, I should note, my solution is pretty verbose. But my use case needs to test window.location.replace() and the outcome. So I couldn't
simply mock window.location.replace. If you just need to mock one of the functions and don't care how the actual href changes, then some of the solutions in the thread will work great with less code.
Working Version
I found that completely polyfilling the window.location object solved all my problems.
window.location polyfill
Use this code and put it anywhere in your test file or setup files:
export class MockWindowLocation {
private _url: URL = new URL();
get href (): string {
return this._url.toString();
}
set href (url: string) {
this._url = new URL(url);
}
get protocol (): string {
return this._url.protocol;
}
get host (): string {
return this._url.host;
}
get hostname (): string {
return this._url.hostname;
}
get origin (): string {
return this._url.origin;
}
get port (): string {
return this._url.port;
}
get pathname (): string {
return this._url.pathname;
}
get hash (): string {
return this._url.hash;
}
get search (): string {
return this._url.search;
}
replace = jest.fn().mockImplementation((url: string) => {
this.href = url;
});
assign = jest.fn().mockImplementation((url: string) => {
this.href = url;
});
reload = jest.fn();
toString(): string {
return this._url.toString();
}
}
Test it
Then you have to delete window.location and set it to the new polyfill:
it('should be able to test window.location', () => {
delete window.location;
Object.defineProperty(window, 'location', {
value: new MockWindowLocation()
});
window.location.href = 'https://example.com/app/#/route/1';
window.location.reload();
expect(window.location.reload).toHaveBeenCalled();
expect(window.location.href).toBe('https://example.com/app/#/route/1');
expect(window.location.pathname).toBe('/app/');
expect(window.location.hash).toBe('#/route/1');
});
This works wonders for me. Hope it helps someone else out.
Other answers are simplier
To re-iterate, there are other answers in this thread that work just fine. I found:
Object.defineProperty(window, 'location', {
writable: true,
value: { reload: jest.fn() },
});
And:
const location: Location = window.location;
delete window.location;
window.location = {
...location,
reload: jest.fn()
};
both to be helpful. But like I said, I needed to spy on replace() and still have the standard functionality of window.location.
Hope this helps someone. Cheers!

Nested describe and behaviour of dynamically created "it"s

I have nested describes in my tests and as usual I am using some beforeEach and before in describes. And one of my describe function calls helper function which creates dynamic tests (DRY). And mocha runs description of nested describe before beforeEach method. And my dynamically created it has comp as undefined.
const checkProps = (comp, propName, expectedvalue) => {
it(`${comp} should have ${propName} equal to ${expectedvalue}`, () => {
expect(comp.prop(propName)).to.equal(expectedvalue);
});
};
describe('Component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps(wrapper, 'title', 'SomeTitle');
});
});
What is the best way todo it? Thanks in advance.
What happens
The Mocha Run Cycle runs all describe callback functions first (...which is also true for other testing frameworks such as Jest and Jasmine).
Then it runs the before hooks, then beforeEach hooks, and finally the it callbacks.
So checkProps runs as part of running the initial describe callbacks, and at that point wrapper is undefined, so as you have noticed the test description says undefined should have....
The beforeEach hook runs before the it callback function runs...but it redefines wrapper so when the it callback runs comp is still undefined and the test fails:
1) Component
prop checking
undefined should have title equal to SomeTitle:
TypeError: Cannot read property 'prop' of undefined
at Context.prop (test/code.test.js:15:19)
Solution
A couple of things need to be changed:
The component name needs to be available when it runs and at that point wrapper doesn't exist yet so you'll have to pass the name yourself.
If you pass an object to checkProps then you can set a wrapper property on the object during beforeEach and access that wrapper property within your test since the object is never redefined.
Here is a working test that should get you closer to what you are trying to do:
import * as React from 'react';
import { shallow } from 'enzyme';
const MyComponent = () => (<div title="SomeTitle">some text</div>);
const checkProps = (name, obj, propName, expectedvalue) => {
it(`${name} should have ${propName} equal to ${expectedvalue}`, () => {
expect(obj.wrapper.prop(propName)).to.equal(expectedvalue); // Success!
});
};
describe('Component', () => {
const obj = {};
beforeEach(() => {
obj.wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps('MyComponent', obj, 'title', 'SomeTitle');
});
});

Why use beforeEach() when writing javascript tests?

I'm learning how to write tests for my code and my instructor explained to put the component being rendered in a beforeEach() function like this...
describe('CommentBox', () => {
let component;
beforeEach(() => {
component = renderComponent(CommentBox);
});
it('has the correct class', () => {
expect(component).to.have.class('comment-box');
});
it('has a text area', () => {
expect(component.find('textarea')).to.exist;
});
it('has a button', () => {
expect(component.find('button')).to.exist;
});
});
Why use beforeEach(). Why not just declare the component variable at the top of the describe function like so...
describe('CommentBox', () => {
const component = renderComponent(CommentBox);
it('has the correct class', () => {
expect(component).to.have.class('comment-box');
});
it('has a text area', () => {
expect(component.find('textarea')).to.exist;
});
it('has a button', () => {
expect(component.find('button')).to.exist;
});
});
This would seem to save a couple extra lines of code, and one less function you would have to write?
The beforeEach runs before each test. This means that each test gets fresh data to test with.
If you do it the other way each test could modify the object and then taint the object for the next test.
Its good practice to not create dependencies between tests so that you can more accurately find bugs or ensure your test actually tests the logic you intended.
I assume that your testing renderComponent.

How to mock React component methods with jest and enzyme

I have a react component(this is simplified in order to demonstrate the issue):
class MyComponent extends Component {
handleNameInput = (value) => {
this.searchDish(value);
};
searchDish = (value) => {
//Do something
}
render() {
return(<div></div>)
}
}
Now I want to test that handleNameInput() calls searchDish with the provided value.
In order to do this I would like to create a jest mock function that replaces the component method.
Here is my test case so far:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
wrapper.searchDish = jest.fn();
wrapper.instance().handleNameInput('BoB');
expect(wrapper.searchDish).toBeCalledWith('BoB');
})
But all I get in the console is SyntaxError:
SyntaxError
at XMLHttpRequest.open (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:458:15)
at run_xhr (node_modules/browser-request/index.js:215:7)
at request (node_modules/browser-request/index.js:179:10)
at DishAdmin._this.searchDish (src/main/react/components/DishAdmin.js:155:68)
at DishAdmin._this.handleNameInput (src/main/react/components/DishAdmin.js:94:45)
at Object.<anonymous> (src/main/react/tests/DishAdmin.test.js:122:24)
So my question is, how do I properly mock component methods with enzyme?
The method can be mocked in this way:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
wrapper.instance().searchDish = jest.fn();
wrapper.update();
wrapper.instance().handleNameInput('BoB');
expect(wrapper.instance().searchDish).toBeCalledWith('BoB');
})
You also need to call .update on the wrapper of the tested component in order to register the mock function properly.
The syntax error was coming from the wrong assingment (you need to assign the method to the instance). My other problems were coming from not calling .update() after mocking the method.
Needs to be replaced wrapper.update(); with wrapper.instance().forceUpdate();
#Miha's answer worked with a small change:
it('handleNameInput', () => {
let wrapper = shallow(<MyComponent/>);
const searchDishMock = jest.fn();
wrapper.instance().searchDish = searchDishMock;
wrapper.update();
wrapper.instance().handleNameInput('BoB');
expect(searchDishMock).toBeCalledWith('BoB');
})

Categories

Resources