Skip to content

Commit 598095f

Browse files
committed
update documentation, rename projects.test file, fix small bug in nav test
1 parent 79eab16 commit 598095f

File tree

4 files changed

+223
-44
lines changed

4 files changed

+223
-44
lines changed

client/components/__test__/Nav.test.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { render } from '@testing-library/react';
33

44
import { NavComponent } from '../Nav';
55

6+
jest.mock('../../i18n');
7+
68
describe('Nav', () => {
79
const props = {
810
newProject: jest.fn(),

client/modules/IDE/actions/__tests__/projects.test.jsx renamed to client/modules/IDE/actions/__tests__/projects.test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import {
1010
mockProjects
1111
} from '../../../../redux_test_stores/test_store';
1212

13-
// look into this
14-
// https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach
15-
1613
const mockStore = configureStore([thunk]);
1714

1815
describe('projects action creator tests', () => {

client/modules/IDE/components/SketchList.test.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ jest.mock('../../../i18n');
2424
*/
2525

2626
describe('<Sketchlist />', () => {
27-
let store;
2827
let container;
2928
const mockStore = configureStore([thunk]);
29+
const store = mockStore(initialTestState);
3030

3131
beforeEach(() => {
3232
// setup a DOM element as a render target
3333
container = document.createElement('div');
3434
document.body.appendChild(container);
3535
axios.get.mockImplementationOnce((x) => Promise.resolve({ data: 'foo' }));
36-
store = mockStore(initialTestState);
3736
});
3837

3938
afterEach(() => {

developer_docs/testing.md

Lines changed: 220 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Many files still don't have tests, so if you're looking to get started as a cont
88
## What's in this document
99
- [Testing dependencies](#testing-dependencies)
1010
- [Useful testing commands](#Useful-testing-commands)
11-
- [When to run tests](#When-to-run-tests)
1211
- [Why write tests](#Why-write-tests)
12+
- [When to run tests](#When-to-run-tests)
1313
- [Writing a test](#Writing-a-test)
1414
- [Files to be aware of](#Files-to-be-aware-of)
1515
- [Testing plain components](#Testing-plain-components)
@@ -55,10 +55,6 @@ npm run test -- Sketchlist.test.js -u
5555

5656
Find more commands in the [Jest documentation](https://jestjs.io/docs/cli).
5757

58-
## When to run tests
59-
60-
Are they actually being run automaticlly???
61-
6258
## Why write tests
6359
- Good place to start if you're learning the codebase because it's harder to mess up production code
6460
- Benefits all future contributors by allowing them to check their changes for errors
@@ -67,20 +63,39 @@ Are they actually being run automaticlly???
6763
- Good practice for large projects
6864
- Many of the existing components don't have tests yet, and you could write one :-)
6965

66+
## When to run tests
67+
68+
When you make a git commit, the tests will be run automatically for you.
69+
70+
When you modify an existing component, it's a good idea to run the test suite to make sure it didn't make any changes that break the rest of the application. If they did break some tests, you would either have to fix a bug component or update the tests to match the new expected functionality.
71+
7072
## Writing a test
7173
Want to get started writing a test for a new file or an existing file, but not sure how?
7274
### For React components
73-
75+
(the below assumes we're using proposed folder structure 1)
7476
1. Make a new file in the ``__tests__`` folder that's directly adjacent to your file. For example, if ``example.jsx`` is in ``src/components``, then you would make a file called ``example.test.jsx`` in ``src/components/__tests__``
7577
2. Check if the component is connected to redux or not.
7678
3. If it is, see the redux section below on how to write tests for that.
7779
4. If it's not, see the section below on writing tests for unconnected components.
7880

7981
### For Redux action creators or reducers
80-
See the redux section below :)
82+
See the [redux section](#Testing-Redux) below :)
83+
84+
### Troubleshooting
85+
1. Check if the component makes any API calls. If it's using axios, jest should already be set up to replace the axios library with a mocked version; however, you may want to [mock](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn) the axios.get() function with your own version so that GET calls "return" whatever data makes sense for that test.
86+
87+
```
88+
axios.get.mockImplementationOnce(
89+
(x) => Promise.resolve({ data: 'foo' })
90+
);
91+
```
92+
You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx).
8193
82-
### For server side code
83-
lol no clue how to do this yet
94+
2. If the component makes use of the formatDate util, some of the functions in that rely on the ``./client/i18n.js`` file that also makes an ajax request, which sometimes leads to an ERRCONNECTED error on the console, even though your tests pass. You can fix it by adding a mock for that specific i18n file:
95+
```
96+
jest.mock('_path_to_file_/i18n');
97+
```
98+
You can see it used in the context of a test [here](../client/modules/IDE/components/SketchList.test.jsx).
8499
85100
## Files to be aware of
86101
@@ -224,7 +239,6 @@ function reduxRender(
224239
```
225240
226241
227-
228242
### redux_test_stores
229243
This folder contains the inital redux states that you can provide to the ``reduxRender`` function when testing. For example, if you want to render the SketchList component with a username of ``happydog`` and some sample sketches, ``redux_test_stores\test_store.js`` contains a definition for that state that you can import and provide to the renderer.
230244
@@ -233,59 +247,226 @@ This folder contains the inital redux states that you can provide to the ``redux
233247
this i dont know much about yet but want to understand
234248
235249
## Testing plain components
236-
If it doesn't export connect()__stuffhere_ or use redux hooks like adfasdf, then testing your component will be simpler and will look like this:
250+
If it doesn't export connect()___ or use redux hooks like ___, then testing your component will be simpler and might look something like this:
251+
252+
```
253+
import React from 'react';
254+
import { unmountComponentAtNode } from 'react-dom';
255+
import { act } from 'react-dom/test-utils';
256+
import { fireEvent, render, screen } from '../../../../test-utils';
257+
import FakePreferences from './index';
258+
259+
/* a helper function to render the components with the
260+
* props that that component needs to be passed in
261+
* if you want to access the rendered component itself
262+
* you'd have to modify it a little to return the what
263+
* gets returned from the render function, along with the props, which is what it's returning now.
264+
* the default props in this can be overwritten by using extraProps
265+
*/
266+
const renderComponent = (extraProps = {}, container) => {
267+
// if we want to overwrite any of these props, we can do it with extraProps because later keys overwrite earlier ones in the spread operator
268+
const props = {
269+
t: jest.fn(),
270+
fontSize: 12,
271+
autosave: false,
272+
setFontSize: jest.fn(),
273+
setAutosave: jest.fn(),
274+
...extraProps
275+
};
276+
render(<FakePreferences {...props} />, { container });
277+
278+
return props;
279+
};
280+
281+
describe('<FakePreferences />', () => {
282+
let container = null;
283+
beforeEach(() => {
284+
// setup a DOM element as a render target
285+
container = document.createElement('div');
286+
container.classList.add('testing-container');
287+
document.body.appendChild(container);
288+
});
289+
290+
afterEach(() => {
291+
// cleanup on exiting
292+
unmountComponentAtNode(container);
293+
container.remove();
294+
container = null;
295+
});
296+
297+
describe('font tests', () => {
298+
it('font size increase button says increase', () => {
299+
let props;
300+
// render the component
301+
act(() => {
302+
props = renderComponent({fontSize: 15}, container);
303+
});
304+
305+
//I do tests here.
306+
//you can access mock functions from props
307+
//for example, props.setFontSize
308+
309+
});
310+
});
311+
```
237312
313+
Consider what you want to test. Some possible things might be:
314+
- User input results in the expected function being called with the expected argument.
315+
```
316+
act(() => {
317+
fireEvent.click(screen.getByTestId("testid"));
318+
});
319+
expect(yourMockFunction).toHaveBeenCalledTimes(1);
320+
expect(yourMockFunction.mock.calls[0][0]).toBe(argument);
321+
```
322+
- User input results in the class's method being called.
323+
```
324+
//component is the return value of calling render()
325+
const spy1 = jest.spyOn(component.instance(), 'func1');
326+
act(() => {
327+
fireEvent.click(screen.getByTestId("testid"));
328+
});
329+
expect(spy1).toHaveBeenCalledTimes(1);
330+
```
331+
- The text or divs that you expect to be on the page are actually there.
332+
- a previously saved snapshot of the HTML matches a snapshot taken during testing.
333+
- what else???? help!
238334
239335
## Testing Redux
240336
241-
split up testing between:
337+
When testing redux, the general guidance [1] seems to suggest splitting up testing between:
242338
1. action creators
243339
2. reducers
244340
3. connected components
245341
342+
Testing reducers and action creators is covered pretty well in [Redux's documentation](https://redux.js.org/recipes/writing-tests). An example of testing an action creator can be found at [projects.test.js](../client/modules/IDE/components/actions/__tests__/projects.test.jsx)
246343
247-
4. unconnected components
344+
### Connected Components
248345
249-
### action creators
250-
write example code here
251-
- can show cassie projects.test.js because that one is working :)
346+
Although it's possible to export the components as unconnected components for testing (and in this case you would just manually pass in the props that redux provides), the codebase is being migrated to use hooks, and in this case, that approach no longer works. It also doesn't work if we render components that have connected subcomponents. Thus, for consistency, we suggest testing all redux components while they're connected to redux. We can do this with redux-mock-store.
252347
253-
### reducers
254-
write example code here
255-
### connected components
256-
3 approaches im trying for sketchlist
257-
- mock all of axios, let it run the action creators as usual, redux-mock-store and then render component
258-
- export unconnected component and use that
259-
- this method didn't work because the subcomponent that was also redux connected failed. I could use shallow rendering but that's not supported in react-testing-library (I think)
260-
- mock getProjects itself so it never calls apiClient at all
348+
This works like so:
349+
1. Import the reduxRender function from ``client/test_utils.js``
350+
2. Configure the mock store.
351+
```
352+
import configureStore from 'redux-mock-store';
353+
import thunk from 'redux-thunk';
261354

262-
each has its own errors :/ i realized that the third approach is flawed because a lot of the functions rely on apiClient. Also, apiClient calls axios.create before any of the tests even run at all. overall, only mocking getProjects is a fragile solution
355+
356+
const mockStore = configureStore([thunk]);
357+
```
358+
3. Create a mock store. There's an initial state that you can import from ``client/redux_test_stores/test_store.js``
359+
```
360+
store = mockStore(initialTestState);
361+
```
362+
3. Render the component with reduxRender and the store that you just created.
363+
```
364+
reduxRender(<SketchList username="happydog1" />, {store, container});
365+
```
366+
4. Test things! You may need to use jest to mock certain functions if the component is making API calls.
367+
368+
All together, it might look something like this.
369+
370+
```
371+
import React from 'react';
372+
import configureStore from 'redux-mock-store';
373+
import thunk from 'redux-thunk';
374+
import { unmountComponentAtNode } from 'react-dom';
375+
import { act } from 'react-dom/test-utils';
376+
import MyComponent from './MyComponent';
377+
import { reduxRender, fireEvent, screen } from '../../../test-utils';
378+
import { initialTestState } from '../../../redux_test_stores/test_store';
379+
380+
describe(<MyComponent />, () => {
381+
let store;
382+
let container;
383+
const mockStore = configureStore([thunk]);
384+
store = mockStore(initialTestState);
385+
386+
beforeEach(() => {
387+
// setup a DOM element as a render target
388+
container = document.createElement('div');
389+
document.body.appendChild(container);
390+
});
391+
392+
afterEach(() => {
393+
// cleanup on exiting
394+
unmountComponentAtNode(container);
395+
container.remove();
396+
container = null;
397+
store.clearActions();
398+
});
399+
400+
it('stuff about the test', () => {
401+
let component;
402+
act(() => {
403+
component = reduxRender(<MyComponent sampleprop="foo" />, {
404+
store,
405+
container
406+
});
407+
});
408+
409+
//your tests go here
410+
});
411+
412+
})
413+
```
414+
415+
Some things to consider testing:
416+
- User input results in the expected redux action.
417+
```
418+
act(() => {
419+
component = reduxRender(<SketchList username="happydog2" />, {
420+
store,
421+
container
422+
});
423+
});
424+
act(() => {
425+
fireEvent.click(screen.getByTestId('toggle-direction-createdAt'));
426+
});
427+
const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }];
428+
429+
expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction));
430+
```
431+
- User input results in the class's method being called.
432+
```
433+
//component is the return value of calling render()
434+
const spy1 = jest.spyOn(component.instance(), 'func1');
435+
act(() => {
436+
fireEvent.click(screen.getByTestId("testid"));
437+
});
438+
expect(spy1).toHaveBeenCalledTimes(1);
439+
```
440+
- The text or divs that you expect to be on the page are actually there.
441+
- a previously saved snapshot of the HTML matches a snapshot taken during testing.
442+
- what else???? help!
263443
264444
## How to handle API calls in tests
265445
266-
doesnt seem to like it when you make calls in a test
267-
so we mock axios
268-
also a little trickery in i18n .use(Backend)
269-
- editor uses axios, we mock the whole library and jest automatically does this since we have a axios.js file in the __mocks__ folder at the root of the client folder.
270-
- the benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called.
271-
- [see this for more](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152)
272-
- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253)
446+
Some tests throw errors if a part of the client-side code tries to make an API call or AJAX request. Our solution to this is to use jest to replace those functions with [mock functions](https://jestjs.io/docs/mock-functions).
447+
448+
The code in question for the client side is mostly related to the axios library. We mock the whole library - jest automatically does this since we have an ``axios.js`` file in the ``__mocks__`` folder at the root of the client folder. [1][2]
449+
450+
The benefit of this is that you can control exactly what happens when any axios function gets called, and you can check how many times it's been called.
451+
452+
A few components also import ``./client/i18n.js`` (or ``./client/utils/formatDate``, which imports the first file), in which the ``i18n.use(Backend)`` line can sometimes throw a sneaky ERRCONNECTED error. You can resolve this by mocking that file as described in [this section](#Troubleshooting).
273453
274454
## Some more background on tests
275455
276456
### test driven development (TDD)
457+
BDD???
277458
278459
### snapshot testing
279-
want to make an example
460+
You can save a snapshot of what the HTML looks like when the component is rendered.
280461
281462
### integration tests
463+
Testing multiple components together. A small example is rendering a parent component and a child component within that.
282464
283465
### unit tests
284-
285-
### mocking functions
286-
Sometimes you might want to mock a function
466+
Most of our tests are of this type. In this, you're testing a the functionality of a single component and no more.
287467
288468
## Internationalization
469+
Project uses i18n.
289470
290471
## Tips
291472
1. Make test fail at least once to make sure it was a meaningful test
@@ -296,6 +477,6 @@ Sometimes you might want to mock a function
296477
- stuff
297478
298479
## References
299-
[1] stuff here
300-
301-
[2] stuff here
480+
-https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach
481+
- [More info on this method](https://stackoverflow.com/questions/51393952/mock-inner-axios-create/51414152#51414152)
482+
- [and this too](https://medium.com/asos-techblog/how-to-test-your-react-redux-application-48d90481a253)

0 commit comments

Comments
 (0)