This is a three part tutorial series in which we will build a simple shopping cart app using React and Apollo Graphql. The idea is to build a table in which the user can choose which Rick and Morty action figures he wants to buy.
- Part 1: Creating an App using React and Apollo Graphql.
- Part 2: Use Apollo to manage the app's local state.
- Part 3: Unit Tests with Enzyme and Apollo Graphql
In this third part we will add unit tests to our components and our resolvers.
Note: usually I think that it is a good practice to write the tests at the same time as you are writing the code (see TDD - Test Driven Development), but I chose not do to this on this tutorial series, because I thought it would be clearer to separate the subjects.
This tutorial builds on top of the code generated in the Part 2. You can get it here.
The complete code for the Part 3 is available in this repository and the website here: https://komyg.github.io/rm-shop-v3/.
Note: this tutorial assumes that you have a working knowledge of React and Typescript.
Getting Started
To begin, clone the repository that we used on the Part 2.
After you cloned the repository, run yarn install
to download the necessary packages.
Configuring Enzyme
In this tutorial we are going to use Enzyme and Jest to run unit tests on our code. The Enzyme configuration below was taken from the Create React App official documentation.
First let's add the necessary packages: yarn add -D enzyme @types/enzyme enzyme-adapter-react-16 react-test-renderer jest-enzyme wait-for-expect
.
Then let's setup our tests by creating the file: src/setupTests.js and pasting the contents below:
import {
configure
} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-enzyme';
configure({
adapter: new Adapter()
});
Also delete the src/setupTests.ts file if you have it.
Running the tests
At any point in this tutorial you can execute the command yarn test
to run the tests we've written so far. You can also add the file name to the end of this command to run a single test suite.
Testing the resolvers
To test our resolvers, we are going to setup a mock Apollo Client and check the inputs and outputs from them. A good way to see what comes in and out of a resolver is to use console.log
statements.
Set unit price
The first resolver we are going to test is the setUnitPrice
. Let's start by creating a test file: resolvers/set-unit-price.resolver.test.ts and then pasting the contents below on it:
import setUnitPrice from './set-unit-price.resolver';
describe('Set Unit Price Resolver', () => {
it('should set the unit price for a regular character', () => {
const mockCharacter: any = {
id: '3',
__typename: 'Character',
name: 'Summer Smith',
};
const result = setUnitPrice(mockCharacter, null, null as any, null);
expect(result).toBe(5);
});
it('should set the unit price for a special character', () => {
const mockCharacter: any = {
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
};
const result = setUnitPrice(mockCharacter, null, null as any, null);
expect(result).toBe(10);
});
});
The purpose of this resolver is to assign the price of 10 USD to Rick and Morty and 5 USD to everyone else. The way that Apollo does this, is by sending every new Character
that comes from the backend through this resolver in the root
param in order to get the unitPrice
value. This is what we are reproducing in our test.
In this case we don't need to setup a mock client for the test to work, however we are telling the compiler that the mockCharacter
and the context
are of the any
type, so that it won't complain that the mockCharacter
is missing some properties and that we can't assign null
to the context.
Increase chosen quantity
Next we will test the increaseChosenQuantity
. To do this, create the file resolvers/increase-chosen-quantity.resolver.test.ts and paste the contents below:
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import fragmentData from '../generated/fragment-matcher.json';
import increaseChosenQuantity from './increase-chosen-quantity.resolver';
import {
IncreaseChosenQuantityMutation,
IncreaseChosenQuantityDocument,
CharacterDataFragment,
CharacterDataFragmentDoc,
GetShoppingCartQuery,
GetShoppingCartDocument,
} from '../generated/graphql';
describe('Add To Cart Resolver', () => {
let cache: InMemoryCache;
let client: ApolloClient<any>;
beforeEach(() => {
// Create mock fragment matcher
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: fragmentData,
});
// Create mock client and cache
cache = new InMemoryCache({ addTypename: false, fragmentMatcher, freezeResults: true });
client = new ApolloClient({
cache,
resolvers: { Mutation: { increaseChosenQuantity } }, // Resolver we want to test
assumeImmutableResults: true,
});
// Initialize the cache with the desired state
cache.writeData({ data: mockData });
});
it('should increase a character chosen quantity', async () => {
const result = await client.mutate<IncreaseChosenQuantityMutation>({
mutation: IncreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
expect(result.data?.increaseChosenQuantity).toBe(true);
const character = client.readFragment<CharacterDataFragment>({
fragment: CharacterDataFragmentDoc,
id: 'Character:1',
});
expect(character?.chosenQuantity).toBe(1);
});
it('should update the shopping cart', async () => {
const result = await client.mutate<IncreaseChosenQuantityMutation>({
mutation: IncreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
expect(result.data?.increaseChosenQuantity).toBe(true);
const shoppingCartQuery = client.readQuery<GetShoppingCartQuery>({
query: GetShoppingCartDocument,
});
expect(shoppingCartQuery?.shoppingCart.numActionFigures).toBe(1);
expect(shoppingCartQuery?.shoppingCart.totalPrice).toBe(10);
});
});
const mockData = {
characters: {
results: [
{
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
species: 'Human',
image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
chosenQuantity: 0,
unitPrice: 10,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
},
],
},
shoppingCart: {
__typename: 'ShoppingCart',
id: btoa('ShoppingCart:1'),
totalPrice: 0,
numActionFigures: 0,
},
};
There is a lot going on in this file, so we are going to break it down:
First we begin by setting up a mock Apollo Client complete with a fragmentMatcher
, an InMemoryCache
and the resolver that we want to test. Note that both the client and the cache should have the same configurations as the real client, but with the addTypename
property as false.
Then we initialize the InMemoryCache
with a mock state by passing the mockData
variable to the cache.writeData
function. It is important to mention that all fields that are part of any query, fragment or mutation that is ran on this test, must be present on the mock data, otherwise the Apollo will throw an error. For example, if we omit the character's name
parameter in the mockData
, then the Apollo will throw an error, because the characterData
fragment that is used inside the increaseChosenQuantity
resolver contains this field.
Once the cache is initialized, we run two tests to see if the Character
and the ShoppingCart
are being successfully updated when the mutation is ran.
Decrease chosen quantity
Next, let's create a test for the decreaseChosenQuantity
resolver. Start by creating the file: resolvers/decrease-chosen-quantity.resolver.test.ts and pasting the contents below:
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import fragmentData from '../generated/fragment-matcher.json';
import {
CharacterDataFragment,
CharacterDataFragmentDoc,
DecreaseChosenQuantityDocument,
DecreaseChosenQuantityMutation,
GetShoppingCartDocument,
GetShoppingCartQuery,
} from '../generated/graphql';
import decreaseChosenQuantity from './decrease-chosen-quantity.resolver';
describe('Add To Cart Resolver', () => {
let cache: InMemoryCache;
let client: ApolloClient<any>;
beforeEach(() => {
// Create mock fragment matcher
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: fragmentData,
});
// Create mock client and cache
cache = new InMemoryCache({ addTypename: false, fragmentMatcher, freezeResults: true });
client = new ApolloClient({
cache,
resolvers: { Mutation: { decreaseChosenQuantity } }, // Resolver we want to test
assumeImmutableResults: true,
});
// Initialize the cache with the desired state
cache.writeData({ data: mockData });
});
it('should decrease a character chosen quantity', async () => {
const result = await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
expect(result.data?.decreaseChosenQuantity).toBe(true);
const character = client.readFragment<CharacterDataFragment>({
fragment: CharacterDataFragmentDoc,
id: 'Character:1',
});
expect(character?.chosenQuantity).toBe(0);
});
it('should update the shopping cart', async () => {
const result = await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
expect(result.data?.decreaseChosenQuantity).toBe(true);
const shoppingCartQuery = client.readQuery<GetShoppingCartQuery>({
query: GetShoppingCartDocument,
});
expect(shoppingCartQuery?.shoppingCart.numActionFigures).toBe(0);
expect(shoppingCartQuery?.shoppingCart.totalPrice).toBe(0);
});
it('should not decrease the chosen quantity below 0', async () => {
await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
const character = client.readFragment<CharacterDataFragment>({
fragment: CharacterDataFragmentDoc,
id: 'Character:1',
});
expect(character?.chosenQuantity).toBe(0);
});
it('should not decrease the shopping cart price and quantity below 0', async () => {
await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
await client.mutate<DecreaseChosenQuantityMutation>({
mutation: DecreaseChosenQuantityDocument,
variables: { input: { id: '1' } },
});
const shoppingCartQuery = client.readQuery<GetShoppingCartQuery>({
query: GetShoppingCartDocument,
});
expect(shoppingCartQuery?.shoppingCart.numActionFigures).toBe(0);
expect(shoppingCartQuery?.shoppingCart.totalPrice).toBe(0);
});
});
const mockData = {
characters: {
results: [
{
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
species: 'Human',
image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
chosenQuantity: 1,
unitPrice: 10,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
},
],
},
shoppingCart: {
__typename: 'ShoppingCart',
id: btoa('ShoppingCart:1'),
totalPrice: 10,
numActionFigures: 1,
},
};
This test is very similar to the one we created for the increaseChosenQuantity
resolver, but in this case the cache starts with an action figure that has already been selected by the user. Also we added two more tests to make sure that we will not decrease the quantities and the price to less than 0.
Get character
Finally, let's add a test for the last resolver: getCharacter
. Create a new file resolvers/get-character.resolver.test.ts and paste the contents below:
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import fragmentData from '../generated/fragment-matcher.json';
import getCharacter from './get-character.resolver';
import { GetCharacterQuery, GetCharacterDocument } from '../generated/graphql';
describe('Add To Cart Resolver', () => {
let cache: InMemoryCache;
let client: ApolloClient<any>;
beforeEach(() => {
// Create mock fragment matcher
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: fragmentData,
});
// Create mock client and cache
cache = new InMemoryCache({ addTypename: false, fragmentMatcher, freezeResults: true });
client = new ApolloClient({
cache,
resolvers: { Query: { getCharacter } }, // Resolver we want to test
assumeImmutableResults: true,
});
// Initialize the cache with the desired state
cache.writeData({ data: mockData });
});
it('should retrieve a character', async () => {
const result = await client.query<GetCharacterQuery>({
query: GetCharacterDocument,
variables: { id: '1' },
});
expect(result.data.getCharacter?.id).toBe('1');
expect(result.data.getCharacter?.name).toBe('Rick Sanchez');
});
});
const mockData = {
characters: {
results: [
{
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
species: 'Human',
image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
chosenQuantity: 1,
unitPrice: 10,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
},
],
},
};
This test just runs the query through the Apollo and checks the result.
Testing the components
Now let's begin testing the components themselves.
App component
First let's begin with the App
component. Create the file: components/app/app.test.tsx and paste the contents below:
import React from 'react';
import { shallow } from 'enzyme';
import App from './app';
describe('App Component', () => {
it('should mount', () => {
const wrapper = shallow(<App />);
expect(wrapper).toBeTruthy();
});
});
This test is just a smoke test to see if anything will break if we mount this component. Since this component doesn't do much else besides instating other components, just this smoke test is enough.
Character data component
Now let's also create a smoke test fo the CharacterData
component in the file: components/character-data/character-data.test.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import CharacterData from './character-data';
describe('Character Data', () => {
it('should mount', () => {
const wrapper = shallow(<CharacterData character={mockCharacter} />);
expect(wrapper).toBeTruthy();
});
});
const mockCharacter: any = {
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
species: 'Human',
image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
chosenQuantity: 1,
unitPrice: 10,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
};
In both tests above, we are using Enzyme's shallow
. By using it, we are telling Enzyme that we just want to mount the top level component. It can and will ignore all sub-components. This is why we don't have to bother on creating mocks for the children of these two components.
The Apollo Mocked Provider
For the next components that we will test we will need the ApolloMockedProvider to simulate graphql queries and mutations. The ApolloMockedProvider
is available on a separate package: yarn add -D @apollo/react-testing
.
Character table component
Now, let's create a new test for the CharacterTable
component. Since it contains a graphql query, we will need to use the MockedProvider
to simulate the graphql elements.
To start, update the CharacterTable
component in the components/character-table/character-table.tsx file with the content below. We've added a few ids
to the components, so it is easier to query for them in the tests:
// Query state management
if (loading) {
return <CircularProgress id='progress' />;
} else if (error) {
return (
<Typography variant='h5' id='error-msg'>
Error retrieving data, please reload the page to try again.
</Typography>
);
} else if (!data || !data.characters || !data.characters.results) {
return (
<Typography variant='h5' id='no-data-msg'>
No data available, please reload the page to try again.
</Typography>
);
}
Now create the file components/character-table/character-table.spec.tsx and paste the content below:
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import CharacterTable from './character-table';
import { MockedProvider, wait } from '@apollo/react-testing';
import { act } from 'react-dom/test-utils';
import { GetCharactersDocument } from '../../generated/graphql';
jest.mock('../character-data/character-data', () => ({
__esModule: true,
default: function CharacterData() {
return <tr />;
},
}));
describe('Character Table', () => {
it('should show a spinner when loading the data', async () => {
let wrapper;
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[]} resolvers={{}}>
<CharacterTable />
</MockedProvider>
);
});
expect(wrapper).toBeTruthy();
expect(wrapper).toContainMatchingElement('#progress');
});
it('should successfully display the character data', async () => {
let wrapper: ReactWrapper;
await act(async () => {
// Mount the component
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockCharacters]} resolvers={{}}>
<CharacterTable />
</MockedProvider>
);
// Wait until the query is resolved
await wait(0);
wrapper.update();
});
expect(wrapper!).toContainMatchingElement('CharacterData');
});
it('should handle an error', async () => {
let wrapper: ReactWrapper;
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockWithError]} resolvers={{}}>
<CharacterTable />
</MockedProvider>
);
await wait(0);
wrapper.update();
});
expect(wrapper!).toContainMatchingElement('#error-msg');
});
it('should handle when there is no data', async () => {
let wrapper: ReactWrapper;
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[emptyMock]} resolvers={{}}>
<CharacterTable />
</MockedProvider>
);
await wait(0);
wrapper.update();
});
expect(wrapper!).toContainMatchingElement('#no-data-msg');
});
});
const mockCharacters = {
request: { query: GetCharactersDocument },
result: {
data: {
characters: {
__typename: 'Characters',
results: [
{
id: '1',
__typename: 'Character',
name: 'Rick Sanchez',
image: 'https://rickandmortyapi.com/api/character/avatar/1.jpeg',
species: 'Human',
chosenQuantity: 0,
unitPrice: 0,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
},
{
id: '2',
__typename: 'Character',
name: 'Morty Smith',
image: 'https://rickandmortyapi.com/api/character/avatar/2.jpeg',
species: 'Human',
chosenQuantity: 0,
unitPrice: 0,
origin: {
id: '1',
__typename: 'Location',
name: 'Earth (C-137)',
},
location: {
id: '20',
__typename: 'Location',
name: 'Earth (Replacement Dimension)',
},
},
],
},
},
},
};
const mockWithError = {
request: { query: GetCharactersDocument },
error: new Error('Network Error'),
};
const emptyMock = {
request: { query: GetCharactersDocument },
result: {
data: {
characters: null,
},
},
};
There is quite a bit going on in this file, so let's break it down:
Test setup
First we created a mock of the CharacterData
component, to make sure that we are testing the CharacterTable
component in isolation (it is important to do this, because we are using mount
instead of shallow
, this way the whole component tree will be mounted).
Notice that the mock itself contains a default
property which returns an functional component, this is because the CharacterData
component is exported as the module default (export default function CharacterData
), so we mock this by using the default
parameter.
Should show a spinner when loading the data
Our first test checks if we show a spinner while loading the data from the graphql server. We do this, by mounting the whole component wrapped by the MockedProvider
. Notice that we used mount
instead of shallow
, this is because the shallow
function would only mount the first level component, which in this case is the MockedProvider
, so we use mount
to mount the whole component tree.
In this test, we don't have to pass any mocks to it, because we are not waiting for them to be resolved. We just want to see if the spinner will be shown when the query is loading.
Should successfully display the character data
In this test we check if we display the CharacterData
components if our data loads successfully (keep in mind that this is not the real CharacterData
component, but rather our mock). In order to do this, we had to configure a mock which contains the expected input and output data that is handled by the Apollo graphql.
Here we also use the wait function make sure our mock resolves so we can make assertions, otherwise we would only see the loading spinner.
Other tests
We have two more tests, one that checks if we can gracefully handle an error and the other when there is no data available (notice that the error mock has an error
parameter instead of a result
parameter).
At the end of the file, we have our mocks. In here, the same rule we applied with resolvers is valid: all of the fields that you requested in a query or a mutation must be returned in the mock. If a single field is missing, Apollo will throw an error.
You can take a look at Apollo's official documentation if you want to know more about the tests.
Note: in this test suite it is very important to set the
resolver
param in theMockedProvider
, even if it is just an empty object. This is because our query has two@client
fields (chosenQuantity
andunitPrice
) and if we don't set it, theMockedProvider
will always throw a ApolloError: Network error: No more mocked responses for the query: query GetCharacters error.Note 2: in some tests we need to use the act function to make sure our component's state is correct before making any assertions.
Character quantity component
In this component, we would like to test that a mutation to increase or decrease the character's quantity is called whenever we click one of the buttons. First let's add an id
property to both so that we can test them more easily. Change the components/character-quantity/character-quantity.tsx file:
<IconButton color='primary' disabled={props.chosenQuantity <= 0} onClick={onDecreaseQty} id='decrease-btn'>
<ChevronLeftIcon />
</IconButton>
<Typography>{props.chosenQuantity}</Typography>
<IconButton color='primary' onClick={onIncreaseQty} id='increase-btn'>
<ChevronRightIcon />
</IconButton>
Now, create the file: components/character-quantity/character-quantity.test.tsx and paste the contents below:
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import CharacterQuantity from './character-quantity';
import { MockedProvider, wait } from '@apollo/react-testing';
import { act } from 'react-dom/test-utils';
import {
IncreaseChosenQuantityDocument,
DecreaseChosenQuantityDocument,
} from '../../generated/graphql';
describe('Character Quantity', () => {
it('should mount', () => {
const wrapper = mount(
<MockedProvider addTypename={false} mocks={[]}>
<CharacterQuantity characterId='1' chosenQuantity={0} />
</MockedProvider>
);
expect(wrapper).toBeTruthy();
});
it('should call a mutation when increasing a character quantity', async () => {
let wrapper: ReactWrapper;
// Grapqhl mock
const mockIncreaseQuantity = {
request: { query: IncreaseChosenQuantityDocument, variables: { input: { id: '1' } } },
result: jest.fn().mockReturnValue({ data: { increaseChosenQuantity: true } }),
};
await act(async () => {
// Mount
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockIncreaseQuantity]}>
<CharacterQuantity characterId='1' chosenQuantity={0} />
</MockedProvider>
);
// Simulate button click
wrapper
.find('#increase-btn')
.first()
.simulate('click');
// Wait until the mutation is called
await wait(0);
});
// Check if the mutation was actually called.
expect(mockIncreaseQuantity.result).toHaveBeenCalled();
});
it('should call a mutation when decreasing a character quantity', async () => {
let wrapper: ReactWrapper;
const mockDecreaseQuantity = {
request: { query: DecreaseChosenQuantityDocument, variables: { input: { id: '1' } } },
result: jest.fn().mockReturnValue({ data: { increaseChosenQuantity: true } }),
};
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockDecreaseQuantity]}>
<CharacterQuantity characterId='1' chosenQuantity={2} />
</MockedProvider>
);
wrapper
.find('#decrease-btn')
.first()
.simulate('click');
await wait(0);
});
expect(mockDecreaseQuantity.result).toHaveBeenCalled();
});
it('should disable the decrease quantity button when the character quantity is 0', () => {
const wrapper = mount(
<MockedProvider addTypename={false} mocks={[]}>
<CharacterQuantity characterId='1' chosenQuantity={0} />
</MockedProvider>
);
expect(wrapper).toBeTruthy();
expect(
wrapper
.find('#decrease-btn')
.first()
.prop('disabled')
).toBe(true);
});
});
Let's breakdown this test:
We've added a function as the result value of both mutations instead of plain objects. The Apollo MockedProvider
supports either objects, functions and promises as the result
property. This way we can test if the mutation was called.
Just like queries, mutations are also executed asynchronously, so we use the await wait(0);
function (after we clicked on the increase or decrease button) to wait until our mutation has finished executing.
Shopping cart component
For this component, we are going to check if it appears when we have one or more action figures selected. To simplify our tests open the file components/shopping-cart-btn/shopping-cart-btn.tsx and add id
param to the <Box />
that is returned when there are no action figures selected:
if (!data || data.shoppingCart.numActionFigures <= 0) {
return <Box className={classes.root} id='empty-btn' />;
}
Let's also add an id
param to the <Box />
that contains the actual button:
return (
<Box className={classes.root} id='shopping-cart-btn'>
{/* [...] */}
</Box>
);
Now create a new file: components/shopping-cart-btn/shopping-cart-btn.test.tsx and paste the contents below:
import React from 'react';
import { act } from 'react-dom/test-utils';
import { GetShoppingCartDocument } from '../../generated/graphql';
import { mount, ReactWrapper } from 'enzyme';
import { MockedProvider, wait } from '@apollo/react-testing';
import ShoppingCartBtn from './shopping-cart-btn';
describe('Shopping Cart Btn', () => {
it('should not show the button when there are 0 action figures selected', async () => {
let wrapper: ReactWrapper;
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockEmptyCart]}>
<ShoppingCartBtn />
</MockedProvider>
);
await wait(0);
wrapper.update();
});
expect(wrapper!).toContainMatchingElement('#empty-btn');
expect(wrapper!).not.toContainMatchingElement('#shopping-cart-btn');
});
it('should show the button when there is 1 or more action figures selected', async () => {
let wrapper: ReactWrapper;
await act(async () => {
wrapper = mount(
<MockedProvider addTypename={false} mocks={[mockShoppingCart]}>
<ShoppingCartBtn />
</MockedProvider>
);
await wait(0);
wrapper.update();
});
expect(wrapper!).not.toContainMatchingElement('#empty-btn');
expect(wrapper!).toContainMatchingElement('#shopping-cart-btn');
});
});
const mockEmptyCart = {
request: { query: GetShoppingCartDocument },
result: {
data: {
shoppingCart: {
__typename: 'ShoppingCart',
id: btoa('ShoppingCart:1'),
totalPrice: 0,
numActionFigures: 0,
},
},
},
};
const mockShoppingCart = {
request: { query: GetShoppingCartDocument },
result: {
data: {
shoppingCart: {
__typename: 'ShoppingCart',
id: btoa('ShoppingCart:1'),
totalPrice: 10,
numActionFigures: 1,
},
},
},
};
This test is similar to the other ones we've written so far: we use await wait(0);
to wait for the query execution, then we check if we are showing the results correctly.
Conclusion
You can now run all the tests by executing the command: yarn test --watchAll
. If all goes well all of them should pass.
Top comments (0)