Unmount wrapper automatically - vue.js

I am using Vitest along with Testing Library - Vue for unit testing my frontend code. I have been following this pattern:
describe("LoadingSpinner", () => {
const defaultWidth = "4rem";
const defaultHeight = "4rem";
it("should have a default height of 4rem when no props are passed", async () => {
const wrapper = render(LoadingSpinner);
const component = await screen.findByRole("status");
expect(component.style.height).toBe(defaultHeight);
wrapper.unmount();
});
//etc...
}
I am wondering if there is a better way to automatically unmount the wrapper, rather than manually calling wrapper.unmount()? If I don't unmount the wrapper, each test creates a new LoadingSpinner and thus screen.findByRole() fails.

Related

Jest - Warning: You called act(async () => ...) without await

I have a test where I'm trying to test a component's default render compared to a render when something (in this case a 0) is in storage. The component should render the same in both cases. To test this, I need to render the component twice.
import { render, waitFor } from "#testing-library/react-native";
import * as React from "react";
import { View } from "react-native";
it("Shouldn't break when rendering twice...", async () => {
const firstRender = await waitFor(() => render(<View />));
const defaultJson = JSON.stringify(firstRender.toJSON());
firstRender.unmount();
putTheThingInAsyncStorage();
const secondRender = await waitFor(() => render(<View />));
const newJson = JSON.stringify(secondRender.toJSON());
expect(newJson).toBe(defaultJson);
// In this particular test case, they should be the same.
expect(newJson).toBe(defaultJson);
removeTheThingFromAsyncStorage();
});
This test works, but the console gives me this nagging warning: Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
Because of this, I've added the await waitFor(...); to the render calls, but I still get the warning. At this point, the warning is lying to me because I am, in fact, using the await keyword so I'm not sure what the issue is.
What am I doing wrong?
This question has been debated a lot here:
https://github.com/callstack/react-native-testing-library/issues/379
Here is my solution:
Create test/asap.js with
const nodePromise = Promise;
module.exports = (r) => nodePromise.resolve().then(r);
Then add to jest.config.js:
moduleNameMapper: {
'^asap\/+.*$': '<rootDir>/test/asap.js',
}
This will short-circuit the asap module which is used by the internal Promise shim of react-native and is not needed for the jest environment.
It's still confusing to me that I was getting the specific warnings that I was getting, but here's what seemed to work for me:
import { render, waitFor } from "#testing-library/react-native";
import * as React from "react";
import { View } from "react-native";
it("Shouldn't break when rendering twice...", async () => {
// DO NOT use await or put render() in a waitFor
const firstRender = render(<View/>);
const defaultJson = JSON.stringify(firstRender.toJSON());
act(() => {
// Unmount should be wrapped in an act, but don't use await
firstRender.unmount();
});
putTheThingInAsyncStorage();
// DO NOT use await or put render() in a waitFor
const secondRender = render(<View/>);
// I did need to use await and waitFor for this toJSON()
await waitFor(() => secondRender.toJSON());
const newJson = JSON.stringify(secondRender.toJSON());
expect(newJson).toBe(defaultJson);
removeTheThingFromAsyncStorage();
});

Mock Linking.openURL in React Native it's never been called

I´m writing some tests for my app and I´m trying to mock Linking module. I'm using jest.
The Linking.canOpenURL mock it's working fine (toHaveBeenCalled is returning true), but openURL mock is never called.
function mockSuccessLinking() {
const canOpenURL = jest
.spyOn(Linking, 'canOpenURL')
.mockImplementation(() => Promise.resolve(true));
const openURL = jest
.spyOn(Linking, 'openURL')
.mockImplementation(() => Promise.resolve(true));
return { canOpenURL, openURL };
}
The problem is that openURL is not been called.
Here is the test:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
expect(openURL).toHaveBeenCalled();
});
And this the hook under test:
export function useApplyToJob(url) {
const [error, setError] = useState(null);
const apply = () => {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
setError(`Don't know how to open ${url}`);
}
});
};
return [apply, { error }];
}
Given canOpenURL returns a promise, you'll need to wait for the async to occur before testing if openURL has been called. react-hooks-testing-library ships a few async utils to help with this.
Generally it's preferred to use waitForNextUpdate or waitForValueToChange as they are a bit more descriptive of what the test is waiting for, but your hook is not updating any state in the successful case, so you will need to use the more general waitFor utility instead:
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
const [apply] = result.current;
// Act
apply();
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});
As a side note, destructuring result.current to access apply is not recommended. It may work now, but it does not take much refactoring before the apply you're calling is using stale values from a previous render.
Similarly, I'd recommend wrapping the apply() call in act, even though it does not update any state right now. It just makes refactoring easier in the future as well as keeping your tests more consistent when you're testing the error case (which will need an act call).
import { renderHook, act } from '#testing-library/react-hooks';
// ...
test('should open url when there is a proper app the open it', async () => {
const { canOpenURL, openURL } = mockSuccessLinking();
const { result, waitFor } = renderHook(() =>
useApplyToJob('https://www.google.com/'),
);
// Act
act(() => {
result.current[0]();
});
// Assert
expect(result.current[1].error).toBeNull();
expect(canOpenURL).toHaveBeenCalled();
await waitFor(() => {
expect(openURL).toHaveBeenCalled();
});
});

vue test url fails on the clicked Button

I wanted to create a simple Test on Vuejs.
The purpose is to click a router-link and prove that the pathname s correct.
Here is the Code:
describe("Menu.vue", () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
test("click on first routes to ./first", async () => {
const wrapper = shallowMount(Menu, {
localVue,
router,
stubs: {
RouterLink: RouterLinkStub,
First
}
});
await wrapper.find("#testFirst").trigger("click");
await Vue.nextTick();
expect(wrapper.find("#testFirst").exists()).toBe(true);
await flushPromises();
expect(location.pathname).toBe("/first")
});
});
The main component is a Menu.vue with router link id testFirst, the error comes from the last line where the expect fails.
The other component is named first.vue.
The first expect passes proving that the #testFirst exists.
it expects"/first" and received "/"
Thanks
Instead of checking location pathname you can test
expect(wrapper.vm.$route.push).toHaveBeenCalledWith({YOUR ROUTE})
For this, you have to mock the $route push method that you can do in stubs while creating a wrapper for your componenet.
mocks: {
$router:{
push:jest.fn()
}
}

Testing a function called on an object with Jest in React Native

EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})

Shadow DOM and testing it via Jasmine

I have a webcomponent that creates a shadow DOM and adds some html to its shadowRoot.
class SomeThing extends HTMLElement {
attachedCallback () {
this.el = this.createShadowRoot();
this.render();
}
render () {
this.el.innerHTML = '<h1>Hello</h1>';
}
}
export default SomeThing;
And I am compiling it with the help of webpack and its babel-core and babel-preset-es2015 plugins.
Also I am using Karma and Jasmine to write my Unit Test. This is what it looks like.
describe('some-thing', function () {
var someElement;
beforeEach(function () {
someElement = document.createElement('some-thing');
});
it('created element should match string representation', function () {
var expectedEl = '<some-thing></some-thing>';
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.innerHTML).toBe(expectedEl);
});
it('created element should have shadow root', function () {
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.querySelector('some-thing').shadowRoot).not.toBeNull();
})
});
I want to see if there is something in the shadowRoot of my element, and want to write test cases for the HTML and events created inside the shadowRoot. But the second test is failing. It is not able to add shadowRoot to the some-element DOM.
If anyone can help me out, that would be helpful.
I am also uploading the full test working project on Github. You can access it via this link https://github.com/prateekjadhwani/unit-tests-for-shadow-dom-webcomponents
Thanks in advance
I had a similar problem testing a web component but in my case I am using lit-element from polymer/lit-element. Lit-element provides life cycle hooks, template rendering using lit-html library (documentation).
So this is my problem and how I solved. I noticed that the component was added and the class executed constructor and I had access to public methods using:
const element = document.querySelector('my-component-name')
element.METHOD();
element.VARIABLE
But it never reached the hook firstUpdated, so I thought the problem was the speed the test executes vs the speed component is created. So I used the promised provided by lit-element API (updateComplete):
Note: I use mocha/chai instead of Jasmine
class MyComponent extends LitElement {
render() {
return html`<h1>Hello</h1>`
}
}
customElements.define('my-component', TodoApp);
let element;
describe('main', () => {
beforeEach(() => {
element = document.createElement("my-component");
document.querySelector('body').appendChild(element);
});
describe('test', () => {
it('Checks that header tag was added to shadowRoot', (done) => {
(async () => {
const res = await element.updateComplete;
const header = element.shadowRoot.querySelector('h1');
assert.notEqual(header, null);
done();
})();
});
});
});
So, my advice is create a promise and resolve it when the render function is executed, use the promise to sync the creation of the component with tests.
I am using this repository to test concepts
https://github.com/correju/polymer-playground