Commit 78d15274 authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch 'justin_ho-update-testing-style-guide-with-new-extended-wrappers' into 'master'

Update docs with newest recommendations on writing specs

See merge request gitlab-org/gitlab!62996
parents 1870bd83 45afaa38
......@@ -105,12 +105,12 @@ Default client accepts two parameters: `resolvers` and `config`.
- `baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (for example, `${gon.relative_url_root}/api/graphql`)
- `assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, assumes that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache throws a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`.
- `fetchPolicy` determines how you want your component to interact with the Apollo cache. Defaults to "cache-first".
### Multiple client queries for the same object
If you are make multiple queries to the same Apollo client object you might encounter the following error: "Store error: the application attempted to write an object with no provided ID but the store already contains an ID of SomeEntity". [This error only should occur when you have made a query with an ID field for a portion, then made another that returns what would be the same object, but is missing the ID field.](https://github.com/apollographql/apollo-client/issues/2510#issue-271829009)
Please note this is being tracked in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/326101) and the documentation will be updated when this issue is resolved.
Please note this is being tracked in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/326101) and the documentation will be updated when this issue is resolved.
## GraphQL Queries
......@@ -1097,7 +1097,7 @@ it('renders a loading state', () => {
const mockApollo = createMockApolloProvider();
const wrapper = createComponent({ mockApollo });
expect(wrapper.find(LoadingSpinner).exists()).toBe(true)
expect(wrapper.findComponent(LoadingSpinner).exists()).toBe(true)
});
it('renders designs list', async () => {
......@@ -1399,7 +1399,6 @@ describe('My Index test with `createMockApollo`', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
fetchLocalUserSpy = null;
});
......
......@@ -463,7 +463,7 @@ Creating a global, mutable wrapper provides a number of advantages, including th
let wrapper;
// this can now be reused across tests
const findMyComponent = wrapper.find(MyComponent);
const findMyComponent = wrapper.findComponent(MyComponent);
// ...
})
```
......@@ -565,16 +565,15 @@ the mounting function (`mount` or `shallowMount`) to be used to mount the compon
function createComponent({ mountFn = shallowMount } = {}) { }
```
1. Wrap calls to `mount` and `shallowMount` in `extendedWrapper`, this exposes `wrapper.findByTestId()`:
1. Use the `mountExtended` and `shallowMountExtended` helpers to expose `wrapper.findByTestId()`:
```javascript
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { SomeComponent } from 'components/some_component.vue';
let wrapper;
const createWrapper = () => { wrapper = extendedWrapper(shallowMount(SomeComponent)); };
const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); };
const someButton = () => wrapper.findByTestId('someButtonTestId');
```
......
......@@ -27,15 +27,15 @@ See [this video](https://youtu.be/-BkEhghP-kM) for an in-depth overview and inve
**Remedy - Try cloning the object that has Vue watchers**
```patch
- expect(wrapper.find(ChildComponent).props()).toEqual(...);
+ expect(cloneDeep(wrapper.find(ChildComponent).props())).toEqual(...)
- expect(wrapper.findComponent(ChildComponent).props()).toEqual(...);
+ expect(cloneDeep(wrapper.findComponent(ChildComponent).props())).toEqual(...)
```
**Remedy - Try using `toMatchObject` instead of `toEqual`**
```patch
- expect(wrapper.find(ChildComponent).props()).toEqual(...);
+ expect(wrapper.find(ChildComponent).props()).toMatchObject(...);
- expect(wrapper.findComponent(ChildComponent).props()).toEqual(...);
+ expect(wrapper.findComponent(ChildComponent).props()).toMatchObject(...);
```
Please note that `toMatchObject` actually changes the nature of the assertion and won't fail if some items are **missing** from the expectation.
......
......@@ -323,17 +323,13 @@ testing the rendered output.
Here's an example of a well structured unit test for [this Vue component](#appendix---vue-component-subject-under-test):
```javascript
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import axios from '~/lib/utils/axios_utils';
import App from '~/todos/app.vue';
const TEST_TODOS = [
{ text: 'Lorem ipsum test text' },
{ text: 'Lorem ipsum 2' },
];
const TEST_TODOS = [{ text: 'Lorem ipsum test text' }, { text: 'Lorem ipsum 2' }];
const TEST_NEW_TODO = 'New todo title';
const TEST_TODO_PATH = '/todos';
......@@ -351,28 +347,27 @@ describe('~/todos/app.vue', () => {
afterEach(() => {
// IMPORTANT: Clean up the component instance and axios mock adapter
wrapper.destroy();
wrapper = null;
mock.restore();
});
// It is very helpful to separate setting up the component from
// its collaborators (for example, Vuex and axios).
const createWrapper = (props = {}) => {
wrapper = extendedWrapper(
shallowMount(App, {
propsData: {
path: TEST_TODO_PATH,
...props,
},
})
);
wrapper = shallowMountExtended(App, {
propsData: {
path: TEST_TODO_PATH,
...props,
},
});
};
// Helper methods greatly help test maintainability and readability.
const findLoader = () => wrapper.find(GlLoadingIcon);
const findLoader = () => wrapper.findComponent(GlLoadingIcon);
const findAddButton = () => wrapper.findByTestId('add-button');
const findTextInput = () => wrapper.findByTestId('text-input');
const findTodoData = () => wrapper.findAll('[data-testid="todo-item"]').wrappers.map(wrapper => ({ text: wrapper.text() }));
const findTodoData = () =>
wrapper
.findAllByTestId('todo-item')
.wrappers.map((item) => ({ text: item.text() }));
describe('when mounted and loading', () => {
beforeEach(() => {
......@@ -401,14 +396,13 @@ describe('~/todos/app.vue', () => {
expect(findTodoData()).toEqual(TEST_TODOS);
});
it('when todo is added, should post new todo', () => {
findTextInput().vm.$emit('update', TEST_NEW_TODO)
it('when todo is added, should post new todo', async () => {
findTextInput().vm.$emit('update', TEST_NEW_TODO);
findAddButton().vm.$emit('click');
return wrapper.vm.$nextTick()
.then(() => {
expect(mock.history.post.map(x => JSON.parse(x.data))).toEqual([{ text: TEST_NEW_TODO }]);
});
await wrapper.vm.$nextTick();
expect(mock.history.post.map((x) => JSON.parse(x.data))).toEqual([{ text: TEST_NEW_TODO }]);
});
});
});
......
......@@ -464,7 +464,6 @@ describe('component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should show a user', async () => {
......
......@@ -249,7 +249,7 @@ it('exists', () => {
wrapper.findByText(/Click Me/i)
// Good (especially for unit tests)
wrapper.find(FooComponent);
wrapper.findComponent(FooComponent);
wrapper.find('input[name=foo]');
wrapper.find('[data-testid="my-foo-id"]');
wrapper.findByTestId('my-foo-id'); // with shallowMountExtended or mountExtended – check below
......@@ -281,7 +281,7 @@ Example:
```javascript
it('exists', () => {
wrapper.find(FooComponent);
wrapper.findComponent(FooComponent);
});
```
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment