From 83531666deeba3d2123af2c641ac0a6e347e03a4 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 27 Aug 2020 18:31:56 +0200 Subject: [PATCH] Migrated to typescript the most complex reducer in the project --- src/container/types.ts | 3 +- src/short-urls/data/index.ts | 7 +- src/short-urls/helpers/{index.js => index.ts} | 4 +- src/short-urls/reducers/shortUrlEdition.ts | 5 +- src/short-urls/reducers/shortUrlMeta.ts | 6 +- src/short-urls/reducers/shortUrlTags.ts | 5 +- src/short-urls/reducers/shortUrlsList.js | 76 ------------ src/short-urls/reducers/shortUrlsList.ts | 108 ++++++++++++++++++ .../reducers/shortUrlsListParams.ts | 11 +- src/visits/reducers/visitCreation.ts | 2 +- ...UrlsList.test.js => shortUrlsList.test.ts} | 88 ++++++++------ 11 files changed, 182 insertions(+), 133 deletions(-) rename src/short-urls/helpers/{index.js => index.ts} (50%) delete mode 100644 src/short-urls/reducers/shortUrlsList.js create mode 100644 src/short-urls/reducers/shortUrlsList.ts rename test/short-urls/reducers/{shortUrlsList.test.js => shortUrlsList.test.ts} (62%) diff --git a/src/container/types.ts b/src/container/types.ts index 1018b5e9..ae26671d 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -8,11 +8,12 @@ import { ShortUrlDeletion } from '../short-urls/reducers/shortUrlDeletion'; import { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition'; import { ShortUrlsListParams } from '../short-urls/reducers/shortUrlsListParams'; import { ShortUrlTags } from '../short-urls/reducers/shortUrlTags'; +import { ShortUrlsList } from '../short-urls/reducers/shortUrlsList'; export interface ShlinkState { servers: ServersMap; selectedServer: SelectedServer; - shortUrlsList: any; + shortUrlsList: ShortUrlsList; shortUrlsListParams: ShortUrlsListParams; shortUrlCreationResult: ShortUrlCreation; shortUrlDeletion: ShortUrlDeletion; diff --git a/src/short-urls/data/index.ts b/src/short-urls/data/index.ts index e8b3d6d8..4997ce44 100644 --- a/src/short-urls/data/index.ts +++ b/src/short-urls/data/index.ts @@ -1,4 +1,4 @@ -import { Nullable } from '../../utils/utils'; +import { Nullable, OptionalString } from '../../utils/utils'; export interface ShortUrlData { longUrl: string; @@ -33,3 +33,8 @@ export interface ShortUrlModalProps { isOpen: boolean; toggle: () => void; } + +export interface ShortUrlIdentifier { + shortCode: string; + domain: OptionalString; +} diff --git a/src/short-urls/helpers/index.js b/src/short-urls/helpers/index.ts similarity index 50% rename from src/short-urls/helpers/index.js rename to src/short-urls/helpers/index.ts index 32a12ad9..e40fa4a3 100644 --- a/src/short-urls/helpers/index.js +++ b/src/short-urls/helpers/index.ts @@ -1,6 +1,8 @@ import { isNil } from 'ramda'; +import { ShortUrl } from '../data'; +import { OptionalString } from '../../utils/utils'; -export const shortUrlMatches = (shortUrl, shortCode, domain) => { +export const shortUrlMatches = (shortUrl: ShortUrl, shortCode: string, domain: OptionalString): boolean => { if (isNil(domain)) { return shortUrl.shortCode === shortCode && !shortUrl.domain; } diff --git a/src/short-urls/reducers/shortUrlEdition.ts b/src/short-urls/reducers/shortUrlEdition.ts index b10fc1ff..6179f98c 100644 --- a/src/short-urls/reducers/shortUrlEdition.ts +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -3,6 +3,7 @@ import { buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; +import { ShortUrlIdentifier } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_START = 'shlink/shortUrlEdition/EDIT_SHORT_URL_START'; @@ -17,10 +18,8 @@ export interface ShortUrlEdition { error: boolean; } -export interface ShortUrlEditedAction extends Action { - shortCode: string; +export interface ShortUrlEditedAction extends Action, ShortUrlIdentifier { longUrl: string; - domain: OptionalString; } const initialState: ShortUrlEdition = { diff --git a/src/short-urls/reducers/shortUrlMeta.ts b/src/short-urls/reducers/shortUrlMeta.ts index a69c35de..ec0a2bca 100644 --- a/src/short-urls/reducers/shortUrlMeta.ts +++ b/src/short-urls/reducers/shortUrlMeta.ts @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import { Dispatch, Action } from 'redux'; -import { ShortUrlMeta } from '../data'; +import { ShortUrlIdentifier, ShortUrlMeta } from '../data'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; @@ -27,9 +27,7 @@ export interface ShortUrlMetaEdition { error: boolean; } -interface ShortUrlMetaEditedAction extends Action { - shortCode: string; - domain?: string | null; +export interface ShortUrlMetaEditedAction extends Action, ShortUrlIdentifier { meta: ShortUrlMeta; } diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index 89ecd15d..823499aa 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -4,6 +4,7 @@ import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { GetState } from '../../container/types'; import { OptionalString } from '../../utils/utils'; +import { ShortUrlIdentifier } from '../data'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -27,10 +28,8 @@ export interface ShortUrlTags { error: boolean; } -export interface EditShortUrlTagsAction extends Action { - shortCode: string; +export interface EditShortUrlTagsAction extends Action, ShortUrlIdentifier { tags: string[]; - domain: OptionalString; } const initialState: ShortUrlTags = { diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js deleted file mode 100644 index f9a23e7f..00000000 --- a/src/short-urls/reducers/shortUrlsList.js +++ /dev/null @@ -1,76 +0,0 @@ -import { handleActions } from 'redux-actions'; -import { assoc, assocPath, reject } from 'ramda'; -import PropTypes from 'prop-types'; -import { shortUrlMatches } from '../helpers'; -import { CREATE_VISIT } from '../../visits/reducers/visitCreation'; -import { SHORT_URL_TAGS_EDITED } from './shortUrlTags'; -import { SHORT_URL_DELETED } from './shortUrlDeletion'; -import { SHORT_URL_META_EDITED, shortUrlMetaType } from './shortUrlMeta'; -import { SHORT_URL_EDITED } from './shortUrlEdition'; - -/* eslint-disable padding-line-between-statements */ -export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; -export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; -export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; -/* eslint-enable padding-line-between-statements */ - -/** @deprecated Use ShortUrl interface instead */ -export const shortUrlType = PropTypes.shape({ - shortCode: PropTypes.string, - shortUrl: PropTypes.string, - longUrl: PropTypes.string, - visitsCount: PropTypes.number, - meta: shortUrlMetaType, - tags: PropTypes.arrayOf(PropTypes.string), - domain: PropTypes.string, -}); - -const initialState = { - shortUrls: {}, - loading: true, - error: false, -}; - -const setPropFromActionOnMatchingShortUrl = (prop) => (state, { shortCode, domain, [prop]: propValue }) => assocPath( - [ 'shortUrls', 'data' ], - state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) ? assoc(prop, propValue, shortUrl) : shortUrl, - ), - state, -); - -export default handleActions({ - [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), - [LIST_SHORT_URLS]: (state, { shortUrls }) => ({ loading: false, error: false, shortUrls }), - [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: {} }), - [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( - [ 'shortUrls', 'data' ], - reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), - state, - ), - [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), - [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), - [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), - [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( - [ 'shortUrls', 'data' ], - state.shortUrls && state.shortUrls.data && state.shortUrls.data.map( - (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) - ? assoc('visitsCount', visitsCount, shortUrl) - : shortUrl, - ), - state, - ), -}, initialState); - -export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => { - dispatch({ type: LIST_SHORT_URLS_START }); - const { listShortUrls } = buildShlinkApiClient(getState); - - try { - const shortUrls = await listShortUrls(params); - - dispatch({ type: LIST_SHORT_URLS, shortUrls, params }); - } catch (e) { - dispatch({ type: LIST_SHORT_URLS_ERROR, params }); - } -}; diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts new file mode 100644 index 00000000..3e54ea6e --- /dev/null +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -0,0 +1,108 @@ +import { assoc, assocPath, reject } from 'ramda'; +import PropTypes from 'prop-types'; +import { Action, Dispatch } from 'redux'; +import { shortUrlMatches } from '../helpers'; +import { CREATE_VISIT, CreateVisitAction } from '../../visits/reducers/visitCreation'; +import { ShortUrl, ShortUrlIdentifier } from '../data'; +import { buildReducer } from '../../utils/helpers/redux'; +import { GetState } from '../../container/types'; +import { ShlinkApiClientBuilder } from '../../utils/services/types'; +import { EditShortUrlTagsAction, SHORT_URL_TAGS_EDITED } from './shortUrlTags'; +import { SHORT_URL_DELETED } from './shortUrlDeletion'; +import { SHORT_URL_META_EDITED, ShortUrlMetaEditedAction, shortUrlMetaType } from './shortUrlMeta'; +import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; +import { ShortUrlsListParams } from './shortUrlsListParams'; + +/* eslint-disable padding-line-between-statements */ +export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; +export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; +export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; +/* eslint-enable padding-line-between-statements */ + +/** @deprecated Use ShortUrl interface instead */ +export const shortUrlType = PropTypes.shape({ + shortCode: PropTypes.string, + shortUrl: PropTypes.string, + longUrl: PropTypes.string, + visitsCount: PropTypes.number, + meta: shortUrlMetaType, + tags: PropTypes.arrayOf(PropTypes.string), + domain: PropTypes.string, +}); + +interface ShortUrlsData { + data: ShortUrl[]; +} + +export interface ShortUrlsList { + shortUrls: ShortUrlsData; + loading: boolean; + error: boolean; +} + +export interface ListShortUrlsAction extends Action { + shortUrls: ShortUrlsData; + params: ShortUrlsListParams; +} + +export type ListShortUrlsCombinedAction = ( + ListShortUrlsAction & EditShortUrlTagsAction & ShortUrlEditedAction & ShortUrlMetaEditedAction & CreateVisitAction +); + +const initialState: ShortUrlsList = { + shortUrls: { + data: [], + }, + loading: true, + error: false, +}; + +const setPropFromActionOnMatchingShortUrl = (prop: keyof T) => ( + state: ShortUrlsList, + { shortCode, domain, [prop]: propValue }: T, +): ShortUrlsList => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls.data.map( + (shortUrl: ShortUrl) => + shortUrlMatches(shortUrl, shortCode, domain) ? { ...shortUrl, [prop]: propValue } : shortUrl, + ), + state, +); + +export default buildReducer({ + [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), + [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true, shortUrls: { data: [] } }), + [LIST_SHORT_URLS]: (_, { shortUrls }) => ({ loading: false, error: false, shortUrls }), + [SHORT_URL_DELETED]: (state, { shortCode, domain }) => assocPath( + [ 'shortUrls', 'data' ], + reject((shortUrl) => shortUrlMatches(shortUrl, shortCode, domain), state.shortUrls.data), + state, + ), + [SHORT_URL_TAGS_EDITED]: setPropFromActionOnMatchingShortUrl('tags'), + [SHORT_URL_META_EDITED]: setPropFromActionOnMatchingShortUrl('meta'), + [SHORT_URL_EDITED]: setPropFromActionOnMatchingShortUrl('longUrl'), + [CREATE_VISIT]: (state, { shortUrl: { shortCode, domain, visitsCount } }) => assocPath( + [ 'shortUrls', 'data' ], + state.shortUrls && state.shortUrls.data && state.shortUrls.data.map( + (shortUrl) => shortUrlMatches(shortUrl, shortCode, domain) + ? assoc('visitsCount', visitsCount, shortUrl) + : shortUrl, + ), + state, + ), +}, initialState); + +export const listShortUrls = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( + params: ShortUrlsListParams = {}, +) => async (dispatch: Dispatch, getState: GetState) => { + dispatch({ type: LIST_SHORT_URLS_START }); + const { listShortUrls } = buildShlinkApiClient(getState); + + try { + const shortUrls = await listShortUrls(params); + + dispatch({ type: LIST_SHORT_URLS, shortUrls, params }); + } catch (e) { + dispatch({ type: LIST_SHORT_URLS_ERROR, params }); + } +}; diff --git a/src/short-urls/reducers/shortUrlsListParams.ts b/src/short-urls/reducers/shortUrlsListParams.ts index 770f81f0..9bc69594 100644 --- a/src/short-urls/reducers/shortUrlsListParams.ts +++ b/src/short-urls/reducers/shortUrlsListParams.ts @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; -import { Action } from 'redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; -import { LIST_SHORT_URLS } from './shortUrlsList'; +import { LIST_SHORT_URLS, ListShortUrlsAction } from './shortUrlsList'; export const RESET_SHORT_URL_PARAMS = 'shlink/shortUrlsListParams/RESET_SHORT_URL_PARAMS'; @@ -16,7 +15,7 @@ export const shortUrlsListParamsType = PropTypes.shape({ }); export interface ShortUrlsListParams { - page: string; + page?: string; tags?: string[]; searchTerm?: string; startDate?: string; @@ -24,11 +23,7 @@ export interface ShortUrlsListParams { orderBy?: object; } -interface ListShortUrlsAction extends Action { - params: ShortUrlsListParams; -} - -const initialState = { page: '1' }; +const initialState: ShortUrlsListParams = { page: '1' }; export default buildReducer({ [LIST_SHORT_URLS]: (state, { params }) => ({ ...state, ...params }), diff --git a/src/visits/reducers/visitCreation.ts b/src/visits/reducers/visitCreation.ts index 735acf6d..3b89318d 100644 --- a/src/visits/reducers/visitCreation.ts +++ b/src/visits/reducers/visitCreation.ts @@ -3,7 +3,7 @@ import { CreateVisit } from '../types'; export const CREATE_VISIT = 'shlink/visitCreation/CREATE_VISIT'; -type CreateVisitAction = Action & CreateVisit; +export type CreateVisitAction = Action & CreateVisit; export const createNewVisit = ({ shortUrl, visit }: CreateVisit): CreateVisitAction => ({ type: CREATE_VISIT, diff --git a/test/short-urls/reducers/shortUrlsList.test.js b/test/short-urls/reducers/shortUrlsList.test.ts similarity index 62% rename from test/short-urls/reducers/shortUrlsList.test.js rename to test/short-urls/reducers/shortUrlsList.test.ts index 08bb0a82..00aa1740 100644 --- a/test/short-urls/reducers/shortUrlsList.test.js +++ b/test/short-urls/reducers/shortUrlsList.test.ts @@ -1,3 +1,4 @@ +import { Mock } from 'ts-mockery'; import reducer, { LIST_SHORT_URLS, LIST_SHORT_URLS_ERROR, @@ -8,26 +9,32 @@ import { SHORT_URL_TAGS_EDITED } from '../../../src/short-urls/reducers/shortUrl import { SHORT_URL_DELETED } from '../../../src/short-urls/reducers/shortUrlDeletion'; import { SHORT_URL_META_EDITED } from '../../../src/short-urls/reducers/shortUrlMeta'; import { CREATE_VISIT } from '../../../src/visits/reducers/visitCreation'; +import { ShortUrl } from '../../../src/short-urls/data'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; describe('shortUrlsListReducer', () => { describe('reducer', () => { it('returns loading on LIST_SHORT_URLS_START', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_START })).toEqual({ - shortUrls: {}, + expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ + shortUrls: { + data: [], + }, loading: true, error: false, })); it('returns short URLs on LIST_SHORT_URLS', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS, shortUrls: { data: [], paginator: {} } })).toEqual({ - shortUrls: { data: [], paginator: {} }, + expect(reducer(undefined, { type: LIST_SHORT_URLS, shortUrls: { data: [] } } as any)).toEqual({ + shortUrls: { data: [] }, loading: false, error: false, })); it('returns error on LIST_SHORT_URLS_ERROR', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR })).toEqual({ - shortUrls: {}, + expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR } as any)).toEqual({ + shortUrls: { + data: [], + }, loading: false, error: true, })); @@ -38,14 +45,16 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, tags: [] }, - { shortCode, tags: [], domain: 'example.com' }, - { shortCode: 'foo', tags: [] }, + Mock.of({ shortCode, tags: [] }), + Mock.of({ shortCode, tags: [], domain: 'example.com' }), + Mock.of({ shortCode: 'foo', tags: [] }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_TAGS_EDITED, shortCode, tags, domain: null })).toEqual({ + expect(reducer(state, { type: SHORT_URL_TAGS_EDITED, shortCode, tags, domain: null } as any)).toEqual({ shortUrls: { data: [ { shortCode, tags }, @@ -53,6 +62,8 @@ describe('shortUrlsListReducer', () => { { shortCode: 'foo', tags: [] }, ], }, + loading: false, + error: false, }); }); @@ -66,21 +77,25 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, meta: { maxVisits: 10 }, domain }, - { shortCode, meta: { maxVisits: 50 } }, - { shortCode: 'foo', meta: null }, + Mock.of({ shortCode, meta: { maxVisits: 10 }, domain }), + Mock.of({ shortCode, meta: { maxVisits: 50 } }), + Mock.of({ shortCode: 'foo', meta: {} }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_META_EDITED, shortCode, meta, domain })).toEqual({ + expect(reducer(state, { type: SHORT_URL_META_EDITED, shortCode, meta, domain } as any)).toEqual({ shortUrls: { data: [ { shortCode, meta, domain: 'example.com' }, { shortCode, meta: { maxVisits: 50 } }, - { shortCode: 'foo', meta: null }, + { shortCode: 'foo', meta: {} }, ], }, + loading: false, + error: false, }); }); @@ -89,17 +104,21 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode }, - { shortCode, domain: 'example.com' }, - { shortCode: 'foo' }, + Mock.of({ shortCode }), + Mock.of({ shortCode, domain: 'example.com' }), + Mock.of({ shortCode: 'foo' }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: SHORT_URL_DELETED, shortCode })).toEqual({ + expect(reducer(state, { type: SHORT_URL_DELETED, shortCode } as any)).toEqual({ shortUrls: { data: [{ shortCode, domain: 'example.com' }, { shortCode: 'foo' }], }, + loading: false, + error: false, }); }); @@ -112,14 +131,16 @@ describe('shortUrlsListReducer', () => { const state = { shortUrls: { data: [ - { shortCode, domain: 'example.com', visitsCount: 5 }, - { shortCode, visitsCount: 10 }, - { shortCode: 'foo', visitsCount: 8 }, + Mock.of({ shortCode, domain: 'example.com', visitsCount: 5 }), + Mock.of({ shortCode, visitsCount: 10 }), + Mock.of({ shortCode: 'foo', visitsCount: 8 }), ], }, + loading: false, + error: false, }; - expect(reducer(state, { type: CREATE_VISIT, shortUrl })).toEqual({ + expect(reducer(state, { type: CREATE_VISIT, shortUrl } as any)).toEqual({ shortUrls: { data: [ { shortCode, domain: 'example.com', visitsCount: 5 }, @@ -127,6 +148,8 @@ describe('shortUrlsListReducer', () => { { shortCode: 'foo', visitsCount: 8 }, ], }, + loading: false, + error: false, }); }); }); @@ -135,15 +158,11 @@ describe('shortUrlsListReducer', () => { const dispatch = jest.fn(); const getState = jest.fn().mockReturnValue({ selectedServer: {} }); - afterEach(() => { - dispatch.mockReset(); - getState.mockClear(); - }); + afterEach(jest.clearAllMocks); it('dispatches proper actions if API client request succeeds', async () => { - const apiClientMock = { - listShortUrls: jest.fn().mockResolvedValue([]), - }; + const listShortUrlsMock = jest.fn().mockResolvedValue([]); + const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); await listShortUrls(() => apiClientMock)()(dispatch, getState); @@ -151,13 +170,12 @@ describe('shortUrlsListReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS, shortUrls: [], params: {} }); - expect(apiClientMock.listShortUrls).toHaveBeenCalledTimes(1); + expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); it('dispatches proper actions if API client request fails', async () => { - const apiClientMock = { - listShortUrls: jest.fn().mockRejectedValue(), - }; + const listShortUrlsMock = jest.fn().mockRejectedValue(undefined); + const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); await listShortUrls(() => apiClientMock)()(dispatch, getState); @@ -165,7 +183,7 @@ describe('shortUrlsListReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS_ERROR, params: {} }); - expect(apiClientMock.listShortUrls).toHaveBeenCalledTimes(1); + expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); }); });