I'm working on a simple React app that has opens a new tab with window.open() for a read-only 'presenter' view. I'm writing a test for the conditional rendering in Jest but can't seem to get window.name to change correctly. Specifically, I'm testing the line in which handleStart() is called.
Here's the code that is being tested:
componentDidUpdate() {
localStorage.setItem('timeRemaining', this.state.timeRemaining);
if (window.name === 'presenter' && this.state.timeRemaining > 0) {
this.handleStart();
}
}
And here is the test as I currently have it:
it('checks the window and state to call #handleStart if started in parent view', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleStart');
global.window.name === 'presenter';
wrapper.setState({ timeRemaining: 100 });
wrapper.update();
expect(spy).toHaveBeenCalled();
});
Turns out I'm a fool and was attempting to assign global.window.name wrong. The test should read
it('checks the window and state to call #handleStart if started in parent view', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleStart');
global.window.name = 'presenter';
wrapper.setState({ timeRemaining: 100 });
wrapper.update();
expect(spy).toHaveBeenCalled();
});
Leaving this question up as it appears to be unique on here and might be helpful to someone. For reference, if you're testing the Window object in Jest you use 'global' as shown here in lieu of 'window'
Related
In react-testing-library you have to render your react component before executing some tests on its elements.
For several tests on the same component, should you avoid
rendering the component multiple times? Or do you have to render it in each
test()/it() block?
Should you select elements of the component (e.g. button) in each test()/it() block, or should you lift the selection, and select only once?
Does it have any impact on the execution time of the tests?
Is one of the approaches a best practice/antipattern?
Why does the last example fail?
For the basic component I have the following testing approaches:
function MyComponent() {
return (
<>
<button disabled>test</button>
<button disabled>another button</button>
</>
);
}
e.g.
describe("MyComponent", () => {
it("renders", async () => {
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
expect(button).toBeInTheDocument();
});
it("is disabled", async () => {
// repetetive render and select, should be avoided or adopted?
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
expect(button).toBeDisabled();
});
});
vs.
describe("MyComponent", () => {
const { getByRole } = render(<MyComponent />);
const button = getByRole("button", { name: /test/i });
it("renders", async () => {
expect(button).toBeInTheDocument();
});
it("is disabled", async () => {
expect(button).toBeDisabled();
});
});
I would expect the second approach to have a faster execution time since the component has to be rendered only once, but I don't know how to measure it and if it is an anti-pattern?
While it seems to be more DRY, if I add another toBeInTheDocument check, it fails.
Why is this the case?
describe("MyComponent", () => {
const { getByRole } = render(<MyComponent />);
const button = screen.getByRole("button", { name: /test/i });
const button2 = screen.getByRole("button", { name: /another button/i });
it("renders", async () => {
expect(button).toBeInTheDocument(); //ok
});
it("is disabled", async () => {
expect(button).toBeDisabled(); // ok
});
it("renders second button", async () => {
expect(button2).toBeInTheDocument(); // fails: element could not be found in the document
});
});
So this approach seems to be more error-prone!?
Each test should be as atomic as possible, meaning that it should not be using anything that other tests are also using and should run with a fresh state. So relating that to your examples, the first one would be the correct pattern.
When you have a test suite that contains sharable state between unit tests e.g. objects or environment variables, the test suite is very prone to errors. The reason for that is; if one of the unit tests happens to mutate one of the shared objects; all of the other unit tests will also be affected by this, causing them to exhibit unwanted behaviour. This can result in test failures where the code is technically correct or even set up landmines for future developers where the addition of new tests which are correct would still result in failures, hence causing major headaches in figuring out why this is happening.
The only exception to this rule would be immutable primitive variables (e.g. string, number, boolean with the use of const keyword) as tests will not be able to mutate them and they are useful for storing reusable ids, text etc.
Ofcourse, repeating the setup of each unit test can make them really clunky, that's why jest offers the beforeEach, beforeAll, afterEach and afterAll functions to extract the repeating logic. However, this opens up the vulnerability of shared state, so do be careful and make sure that all state is refreshed before any tests are kicked off. Ref.
For the last question as to why your last unit test in the last example is failing - it appears that you are using getByRole to look for button text. You should be using getByText instead. getByRole is used with role attributes (e.g. <button role="test">test</button>) which you don't seem to be using.
I'm writing tests using Jest and React Testing Library. I had failing tests and I realized that if I change the order of the tests, it will work. I'm guessing this is because the tests aren't properly isolated and one test might affect the other.
I am calling:
afterEach(() => {
cleanup()
jest.resetAllMocks()
})
I have a test that looks like this:
it('calls API when submitted', async () => {
render(<SignUp />)
fillAllVerificationFieldsWithTestData()
validateUser.mockResolvedValueOnce({ id: 123 })
const signupButton = screen.getByTestId(
'sign-up-verification-button',
)
userEvent.click(signupButton)
await waitFor(() => expect(validateUser).toHaveBeenCalledTimes(1))
})
If I create the exact same test or run a similar test after this one with a userEvent.click, I get an error:
Unable to fire a "mouseMove" event - please provide a DOM element.
I looked in the #testing-library/user-event library and I see this code:
const userEvent = {
click(element) {
const focusedElement = element.ownerDocument.activeElement;
const wasAnotherElementFocused =
focusedElement !== element.ownerDocument.body &&
focusedElement !== element;
if (wasAnotherElementFocused) {
fireEvent.mouseMove(focusedElement);
fireEvent.mouseLeave(focusedElement);
}
I noticed that element.ownerDocument.activeElement is null wasAnotherElementFocused is true and so it throws the error.
The first time I run the test it isn't null so it works.
Do I need some extra clean up between tests? If I use fireEvent:
fireEvent(signupButton,
new MouseEvent('click', {
bubbles: true,
}),
)
It works but I'm afraid I'm doing something wrong and not isolating my tests correctly.
EDIT:
Here is the code for the fillAllVerificationFieldsWithTestData:
export const fillAllVerificationFieldsWithTestData = () => {
const { given_name, family_name, zip, social, loanNumber } = {
given_name: screen.getByTestId('given_name'),
family_name: screen.getByTestId('family_name'),
zip: screen.getByTestId('zip'),
social: screen.getByTestId('last4ssn'),
loanNumber: screen.getByTestId('loan_number'),
}
userEvent.type(given_name, 'FirstName')
userEvent.type(family_name, 'LastName')
userEvent.type(zip, '77025')
userEvent.type(social, '1234')
userEvent.type(loanNumber, '1112223333')
}
and screen is imported from #testing-library/react and I import validate user like this:
import { validateUser } from '../../../services/auth'
jest.mock('../../../services/auth')
So, I just faced this problem, and what fixed it for me was wrapping any code that causes a React state change (which your userEvent code presumably does) in act. So, your initial test would look like this:
it('calls API when submitted', async () => {
render(<SignUp />)
fillAllVerificationFieldsWithTestData()
validateUser.mockResolvedValueOnce({ id: 123 })
const signupButton = screen.getByTestId(
'sign-up-verification-button',
)
await act(() => userEvent.click(signupButton))
await waitFor(() => expect(validateUser).toHaveBeenCalledTimes(1))
})
Can you give this a try, applying similar changes to your fillAllVerificationFieldsWithTestData function, and let us know how it turned out?
This is a bug that was reported in the testing-library/user-event GitHub and it should be fixed as of v11.0.1. It's specific to userEvent, which is why fireEvent works.
Note that you shouldn't need to call cleanup if you're using Jest because it will get called for you.
I've been trying to mock a test in jest through the methods that they have on their documentation. By mocking the whole class but I can't seem to get it to work properly.
https://jestjs.io/docs/en/es6-class-mocks
jest.mock('../../../../../src/SubscriptionOrder');
SubscriptionOrder.prototype.createChargebeeSubscription = jest.fn(() => 'response');
const test = new SubscriptionOrder(
'subscription',
[{}],
'errorMethods',
'customerMethods',
);
test.createChargebeeSubscription();
I'd expect this to mock the createChargebeeSubscription method and return the string response but it seems to be returning undeifined
Then this is the piece of code I'm trying to run a test for as well.
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods,
);
const response = await subscriptionOrder.createChargebeeSubscription(token);
this.setState({ successfulSubmit: response });
I want to update the state to the string response but getting undefined instead. so it appears I'm kinda mocking something but just not properly.
You can use spyOn as follows to do the mocking for you. I also recommend that you set up and tear down this spy once you are finished.
So here's a sample piece of code which will do what you want:
describe('createChargebeeSubscription() method behaviour', () => {
let createChargebeeSubscriptionSpy;
let testResponse;
beforeAll(() => {
// Lets create an instance of your class first
const subscriptionOrder = new SubscriptionOrder(
'subscription',
subscriptionRequest,
errorMethods,
customerMethods
);
// Now use a spy to mock the return value from your function
createChargebeeSubscriptionSpy = jest.spyOn(subscriptionOrder, 'createChargebeeSubscription').mockImplementation(() => {
return 'response';
});
// Finally invoke the method being tested
testResponse = subscriptionOrder.createChargebeeSubscription();
});
afterAll(() => {
// Restore the functionality (ie. disable the spy) of your method
createChargebeeSubscriptionSpy.mockRestore();
});
it('verifies that the expected response was returned', () => {
expect(testResponse).toBe('response');
});
});
I'm trying to test a form. When the form is submitted it should set a state on error: true and then a div with an error information should appear. My test looks like this:
outer = shallow(<Search />);
it("should not submit a form and display an error if title or author is empty", () => {
const Children = outer.props().children({});
const wrapper = mount(Children);
const button = wrapper.find("form button");
button.simulate("submit", {
preventDefault() {
outer.setState({ error: true });
}
});
expect(wrapper.find("div.error")).toHaveLength(1);
});
Unfortunately it doesn't work. I am new to unit testing and I have no idea if I'm doing it correctly and how should I fix that.
I think I also should get somehow inputs values but don't know how.
This is the sample to set value to input elements:
it('Should capture firstname correctly onChange', function(){
const component = mount(<Form />);
const input = component.find('input').at(0);
input.instance().value = 'hello';
input.simulate('change');
expect(component.state().firstname).toEqual('hello');
})
But it may not work because of different other reasons, make sure you have initialized enzyme components in beforeAll(). Try to read enzyme's examples about this topic.
I have a component which detect outside Click which is implemented like this https://medium.com/#pitipatdop/little-neat-trick-to-capture-click-outside-react-component-5604830beb7f
and i am using that component for Dropdown component so that it must close on outside click.
I followed this comment to unit test it but still no luck.
https://github.com/airbnb/enzyme/issues/426#issuecomment-431195329
outside click is not been captured please help me with this.
My test case is like this
it("should check outside click", () => {
const outerNode = document.createElement('div');
outerNode.className = "outerDiv";
document.body.appendChild(outerNode);
wrapper = mount(<Dropdown {...props}>{props.children}</Dropdown>, { attachTo: outerNode })
const obj = wrapper.find("button")
obj.instance().dispatchEvent(new Event('click', { bubbles: true }));
expect(wrapper.state().activated).toBe(true);
outerNode.dispatchEvent(new Event('click', { bubbles: true })); // this is not working as expected
expect(wrapper.state().activated).toBe(false);
}
outside element click is not dispatched.