Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions 01 Testing components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,11 @@ Since we want to trigger the `change` event, we simply need to call the `fireEve

## Running asynchronous calls and unit tests

Now let us modify our component so that it performs a call against an API to retrieve some data after initialization. This is a side effect and can be implemented using a `useEffect` hook. We will also import the `getListOfFruit` method from `myApi` folder, which simulates an async call to retrieve a list of pieces of fruit.
Now let us modify our component so that it performs a call against an API to retrieve some data after initialization. This is a side effect and can be implemented using a `useEffect` hook. We will also import the `getListOfFruit` method from `myFruitApi` folder, which simulates an async call to retrieve a list of pieces of fruit.

```diff
import * as React from 'react';
+import { getListOfFruit } from '../myApi';
+import { getListOfFruit } from '../myFruitApi';

export interface Props {
nameFromProps: string;
Expand Down Expand Up @@ -250,13 +250,13 @@ So now our component will always perform a (fake) fetch call on initialization,
import * as React from 'react';
-import { render, fireEvent } from '@testing-library/react';
+import { render, fireEvent, waitForElement } from '@testing-library/react';
+import * as myApi from '../myApi';
+import * as myFruitApi from '../myFruitApi';
import { MyComponent, Props } from './myComponent';
...
+ it('should display the list of fruits after resolving the api call on initialization', async () => {
+ // Arrange
+ const getListOfFruitMock = jest
+ .spyOn(myApi, 'getListOfFruit')
+ .spyOn(myFruitApi, 'getListOfFruit')
+ .mockResolvedValue(['Melon', 'Apple', 'Pear']);
+
+ // Act
Expand All @@ -282,13 +282,13 @@ However, if we run the code (and we are using React 16.8.6 or lower), we will se
import * as React from 'react';
-import { render, fireEvent, waitForElement } from '@testing-library/react';
+import { render, fireEvent, waitForElement, act, RenderResult } from '@testing-library/react';
import * as myApi from '../myApi';
import * as myFruitApi from '../myFruitApi';
import { MyComponent, Props } from './myComponent';
...
it('should display the list of fruits after resolving the api call on initialization', async () => {
// Arrange
const getListOfFruitMock = jest
.spyOn(myApi, 'getListOfFruit')
.spyOn(myFruitApi, 'getListOfFruit')
.mockResolvedValue(['Melon', 'Apple', 'Pear']);

// Act
Expand All @@ -315,7 +315,7 @@ This fixes the issue with this test. Technically, the other tests also share the

```diff
import * as React from 'react';
-import { getListOfFruit } from '../myApi';
-import { getListOfFruit } from '../myFruitApi';
+import { MyFruits } from './myFruits';

export interface Props {
Expand Down Expand Up @@ -355,7 +355,7 @@ export const MyComponent: React.FunctionComponent<Props> = props => {
And the code for our new `MyFruits` component can be found below
```javascript
import * as React from 'react';
import { getListOfFruit } from '../myApi';
import { getListOfFruit } from '../myFruitApi';

export const MyFruits = (props) => {
const [fruits, setFruits] = React.useState([]);
Expand Down Expand Up @@ -384,7 +384,7 @@ The first thing we are going to do is disable the last test we coded as it does
- it('should display the list of fruits after resolving the api call on initialization', async () => {
// Arrange
const getListOfFruitMock = jest
.spyOn(myApi, 'getListOfFruit')
.spyOn(myFruitApi, 'getListOfFruit')
.mockResolvedValue(['Melon', 'Apple', 'Pear']);

// Act
Expand All @@ -395,7 +395,7 @@ And now, in order to mock our component, let us just mock the whole module that
```diff
import * as React from 'react';
import { render, fireEvent, waitForElement, act, RenderResult } from '@testing-library/react';
import * as myApi from '../myApi';
import * as myFruitApi from '../myFruitApi';
import { MyComponent, Props } from './myComponent';

+jest.mock('./myFruits.tsx', () => ({
Expand Down
17 changes: 16 additions & 1 deletion 01 Testing components/config/webpack/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = merge(
{
context: helpers.resolveFromRootPath('src'),
resolve: {
extensions: ['.js', '.ts', '.tsx'],
extensions: ['.js', '.ts', '.tsx', '.css', '.scss'],
},
entry: {
app: ['./index.tsx'],
Expand All @@ -25,6 +25,21 @@ module.exports = merge(
babelCore: '@babel/core',
},
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
},
],
},
optimization: {
Expand Down
61 changes: 43 additions & 18 deletions 01 Testing components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,61 @@
"start": "webpack-dev-server --config ./config/webpack/dev.js",
"clean": "rimraf dist",
"build": "npm run clean && webpack --config ./config/webpack/prod.js",
"test": "jest -c ./config/test/jest.json --verbose",
"test": "jest -c ./config/test/jest.json --verbose --coverage",
"test:watch": "jest -c ./config/test/jest.json --verbose --watchAll -i"
},
"author": "arp82",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2",
"@material-ui/core": "^4.1.3",
"axios": "^0.19.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1"
"@types/react-redux": "^7.1.7",
"@types/redux": "^3.6.0",
"axios": "^0.19.2",
"install": "^0.13.0",
"npm": "^6.14.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.1",
"react-spinners": "^0.8.1",
"redux": "^4.0.5",
"sinon": "^9.0.2",
"style-loader": "^1.1.3"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"@testing-library/react": "^8.0.7",
"@types/jest": "^24.0.13",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@testing-library/react": "^8.0.9",
"@testing-library/react-hooks": "^3.2.1",
"@types/enzyme": "^3.10.5",
"@types/jest": "^24.9.1",
"@types/react": "^16.9.33",
"@types/react-dom": "^16.9.6",
"@types/react-router-dom": "^4.3.4",
"awesome-typescript-loader": "^5.2.1",
"babel-loader": "^8.1.0",
"babel-plugin-transform-runtime": "^6.23.0",
"css-loader": "^3.5.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.8.0",
"jest": "^24.9.0",
"node-sass": "^4.13.1",
"react-test-renderer": "^16.13.1",
"redux-mock-store": "^1.5.4",
"rimraf": "^2.6.3",
"ts-jest": "^24.0.2",
"typescript": "^3.5.2",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.0",
"webpack-merge": "^4.2.1"
"sass-loader": "^8.0.2",
"source-map-loader": "^0.2.4",
"ts-jest": "^24.3.0",
"ts-loader": "^6.2.2",
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2"
}
}
3 changes: 3 additions & 0 deletions 01 Testing components/src/__mocks__/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
get: jest.fn().mockResolvedValue({ data: [] }as any),
};
4 changes: 2 additions & 2 deletions 01 Testing components/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as React from 'react';
import { MyComponent } from './myComponent';

export const App: React.FunctionComponent = props => (
export const App: React.FunctionComponent = () => (
<div>
<MyComponent nameFromProps="Fruit User" />
<MyComponent />
</div>
);
22 changes: 18 additions & 4 deletions 01 Testing components/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './app';
import React from "react";
import ReactDOM from "react-dom";
import "./styles/style.css";
import { App } from "./app";

import { createStore } from "redux";
import { Provider } from "react-redux";

import { rootReducer } from "./store/contact/index";

const store = createStore(rootReducer)

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

ReactDOM.render(<App />, document.getElementById('root'));
7 changes: 6 additions & 1 deletion 01 Testing components/src/myApi/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { getListOfFruit } from './myApi';
export {
getContactList,
updateContact,
deleteContact,
addContact,
} from './myContactApi';
15 changes: 0 additions & 15 deletions 01 Testing components/src/myApi/myApi.ts

This file was deleted.

12 changes: 0 additions & 12 deletions 01 Testing components/src/myApi/myBackEndApiEndpoint.ts

This file was deleted.

37 changes: 37 additions & 0 deletions 01 Testing components/src/myApi/myContactApi.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
getContactList,
deleteContact,
addContact,
updateContact,
} from './myContactApi';

describe('contact Api', () => {
it('update contact', () => {
const contact = {
id: '1',
name: 'Leanne Graham',
email: 'aaa@april.biz',
};
updateContact(contact).then((u) => expect(Promise.resolve(u.data)).not.toBeNull());
});

it('loads all contacts', () => {
getContactList().then((u) => expect(Promise.resolve(u.data)).not.toBeNull());
});
it('add contact', () => {
const contact = {
id: '',
name: 'Ervin Howell',
email: 'Shanna@melissa.tv',
};
expect(addContact(contact).then((u) => expect(Promise.resolve(u.data)).not.toBeNull()));
});
it('delete contact', () => {
const contact = {
id: '2',
name: 'Ervin Howell',
email: 'Shanna@melissa.tv',
};
expect(deleteContact(contact).then((u) => expect(Promise.resolve(u.data)).not.toBeNull()));
});
});
22 changes: 22 additions & 0 deletions 01 Testing components/src/myApi/myContactApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import axios from 'axios/index';
import { Contact as ContactType } from '../store/contact/types';

export const url = 'https://jsonplaceholder.typicode.com/users';

// Get all contacts
export const getContactList = () =>
axios.get(url);

// Delete contact
export const deleteContact = (contact: ContactType) =>
axios.delete(`${url}/${contact.id}`);

// Update existing contact
export const updateContact = (contact: ContactType) =>
axios.put(`${url}/${contact.id}`, contact);


// Create new contact
export const addContact = (contact: ContactType) =>
axios.post(url, contact);

5 changes: 4 additions & 1 deletion 01 Testing components/src/myComponent/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export { MyComponent } from './myComponent';
export { MyComponent } from './myComponent';
export { MyAddContact } from './myAddContact';
export { MyContact } from './myContact';
export { MyLoading } from './myLoading';
47 changes: 47 additions & 0 deletions 01 Testing components/src/myComponent/myAddContact.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { MyAddContact, Props } from './myAddContact';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

const baseProps: Props = {
onAdd: jest.fn(),
};

describe('MyAddContact', () => {
let props: Props;
let appWrapper;
beforeEach(() => {
props = { ...baseProps };
appWrapper = mount(<MyAddContact {...props} />);
});

it('Should render without errors', () => {
expect(appWrapper.length).toBe(1);
});

it('Set the email and name and trigger onAdd "', () => {
const { getByTestId } = render(<MyAddContact {...props} />);

const inputElementName = getByTestId('add-input-name') as HTMLInputElement;

const inputElementEmail = getByTestId(
'add-input-email'
) as HTMLInputElement;

const buttonElementAdd = getByTestId('add-button-add') as HTMLButtonElement;

fireEvent.change(inputElementName, { target: { value: 'John' } });
fireEvent.change(inputElementEmail, {
target: { value: 'John@gmail.com' },
});
buttonElementAdd.click();

expect(inputElementName.value).toEqual('John');
expect(inputElementEmail.value).toEqual('John@gmail.com');
expect(props.onAdd).toBeCalledTimes(1);
});

});
Loading