From ad90a057721440fa35e24c2645482fc4120adad0 Mon Sep 17 00:00:00 2001 From: "Leon.kov" Date: Wed, 22 Nov 2023 17:27:08 +0300 Subject: [PATCH] feat: add thunk tests \ function for testing thunks --- .gitignore | 1 + config/jest/setupTests.ts | 1 - json-server/db.json | 14 ++-- .../selectors/getCounter/getCounter.test.ts | 5 ++ .../getLoginError/getLoginError.test.ts | 16 +++++ .../getLoginIsLoading/getLoginLoading.test.ts | 16 +++++ .../getLoginPassword/getLoginPassword.test.ts | 16 +++++ .../getLoginUsername/getLoginUsername.test.ts | 16 +++++ .../loginByUsername/loginByUsername.test.ts | 67 +++++++++++++++++++ .../loginByUsername/loginByUsername.ts | 7 +- .../model/slice/loginSlice.test.ts | 21 ++++++ .../tests/TestAsyncThunk/TestAsyncThunk.ts | 26 +++++++ src/shared/ui/Button/Button.test.tsx | 2 +- 13 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 src/features/AuthByUsername/model/selectors/getLoginError/getLoginError.test.ts create mode 100644 src/features/AuthByUsername/model/selectors/getLoginIsLoading/getLoginLoading.test.ts create mode 100644 src/features/AuthByUsername/model/selectors/getLoginPassword/getLoginPassword.test.ts create mode 100644 src/features/AuthByUsername/model/selectors/getLoginUsername/getLoginUsername.test.ts create mode 100644 src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts create mode 100644 src/features/AuthByUsername/model/slice/loginSlice.test.ts create mode 100644 src/shared/lib/tests/TestAsyncThunk/TestAsyncThunk.ts diff --git a/.gitignore b/.gitignore index e515819..a12be32 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules /yarn.lock .package-lock.json /package-lock.json +/.package-lock.json /storybook-static/project.json /storybook-static/favicon.ico diff --git a/config/jest/setupTests.ts b/config/jest/setupTests.ts index fd8c666..c6e8def 100644 --- a/config/jest/setupTests.ts +++ b/config/jest/setupTests.ts @@ -1,3 +1,2 @@ -// Такой файл вы могли наблюдать при create-react-app import '@testing-library/jest-dom'; import 'regenerator-runtime/runtime'; diff --git a/json-server/db.json b/json-server/db.json index 824beed..fece120 100644 --- a/json-server/db.json +++ b/json-server/db.json @@ -1,26 +1,26 @@ { "posts": [ { - "id": 1, + "id": "1", "title": "json-server", - "userId": 1 + "userId": "1" }, { - "id": 2, + "id": "2", "title": "json-server", - "userId": 2 + "userId": "2" } ], "comments": [ { - "id": 1, + "id": "1", "body": "some comment", - "postId": 1 + "postId": "1" } ], "users": [ { - "id": 1, + "id": "1", "username": "admin", "password": "123" } diff --git a/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts b/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts index 76cec35..98c226e 100644 --- a/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts +++ b/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts @@ -9,4 +9,9 @@ describe('getCounter', () => { }; expect(getCounter(state as StateSchema)).toEqual({ value: 10 }); }); + + test('should work with empty state', () => { + const state:DeepPartial = {}; + expect(getCounter(state as StateSchema)).toEqual(undefined); + }); }); diff --git a/src/features/AuthByUsername/model/selectors/getLoginError/getLoginError.test.ts b/src/features/AuthByUsername/model/selectors/getLoginError/getLoginError.test.ts new file mode 100644 index 0000000..581d81e --- /dev/null +++ b/src/features/AuthByUsername/model/selectors/getLoginError/getLoginError.test.ts @@ -0,0 +1,16 @@ +import { DeepPartial } from '@reduxjs/toolkit'; +import { StateSchema } from 'app/providers/StoreProvider'; +import { getLoginError } from './getLoginError'; + +describe('getLoginError.test', () => { + test('should return error', () => { + const state:DeepPartial = { + loginForm: { error: 'error' }, + }; + expect(getLoginError(state as StateSchema)).toEqual('error'); + }); + test('should work with empty state', () => { + const state:DeepPartial = {}; + expect(getLoginError(state as StateSchema)).toEqual(undefined); + }); +}); diff --git a/src/features/AuthByUsername/model/selectors/getLoginIsLoading/getLoginLoading.test.ts b/src/features/AuthByUsername/model/selectors/getLoginIsLoading/getLoginLoading.test.ts new file mode 100644 index 0000000..d1e851a --- /dev/null +++ b/src/features/AuthByUsername/model/selectors/getLoginIsLoading/getLoginLoading.test.ts @@ -0,0 +1,16 @@ +import { DeepPartial } from '@reduxjs/toolkit'; +import { StateSchema } from 'app/providers/StoreProvider'; +import { getLoginIsLoading } from './getLoginIsLoading'; + +describe('getLoginLoading.test', () => { + test('should return loading', () => { + const state: DeepPartial = { + loginForm: { isLoading: true }, + }; + expect(getLoginIsLoading(state as StateSchema)).toEqual(true); + }); + test('should work with empty state', () => { + const state: DeepPartial = {}; + expect(getLoginIsLoading(state as StateSchema)).toEqual(false); + }); +}); diff --git a/src/features/AuthByUsername/model/selectors/getLoginPassword/getLoginPassword.test.ts b/src/features/AuthByUsername/model/selectors/getLoginPassword/getLoginPassword.test.ts new file mode 100644 index 0000000..58764cf --- /dev/null +++ b/src/features/AuthByUsername/model/selectors/getLoginPassword/getLoginPassword.test.ts @@ -0,0 +1,16 @@ +import { DeepPartial } from '@reduxjs/toolkit'; +import { StateSchema } from 'app/providers/StoreProvider'; +import { getLoginPassword } from './getLoginPassword'; + +describe('getLoginPassword.test', () => { + test('should return loading', () => { + const state: DeepPartial = { + loginForm: { password: '123' }, + }; + expect(getLoginPassword(state as StateSchema)).toEqual('123'); + }); + test('should work with empty state', () => { + const state: DeepPartial = {}; + expect(getLoginPassword(state as StateSchema)).toEqual(''); + }); +}); diff --git a/src/features/AuthByUsername/model/selectors/getLoginUsername/getLoginUsername.test.ts b/src/features/AuthByUsername/model/selectors/getLoginUsername/getLoginUsername.test.ts new file mode 100644 index 0000000..94740b3 --- /dev/null +++ b/src/features/AuthByUsername/model/selectors/getLoginUsername/getLoginUsername.test.ts @@ -0,0 +1,16 @@ +import { DeepPartial } from '@reduxjs/toolkit'; +import { StateSchema } from 'app/providers/StoreProvider'; +import { getLoginUsername } from './getLoginUsername'; + +describe('getLoginUsername.test', () => { + test('should return loading', () => { + const state: DeepPartial = { + loginForm: { username: 'name' }, + }; + expect(getLoginUsername(state as StateSchema)).toEqual('name'); + }); + test('should work with empty state', () => { + const state: DeepPartial = {}; + expect(getLoginUsername(state as StateSchema)).toEqual(''); + }); +}); diff --git a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts new file mode 100644 index 0000000..88aebec --- /dev/null +++ b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts @@ -0,0 +1,67 @@ +import axios from 'axios'; +import { userActions } from 'entities/User'; +import { TestAsyncThunk } from 'shared/lib/tests/TestAsyncThunk/TestAsyncThunk'; +import { loginByUsername } from './loginByUsername'; + +jest.mock('axios'); + +const mockedAxios = jest.mocked(axios, true); + +describe('loginByUsername.test', () => { + // let dispatch: Dispatch; + // let getState: () => StateSchema; + // + // beforeEach(() => { + // dispatch = jest.fn(); + // getState = jest.fn(); + // }); + // test('loginByUsername async thunk test', async () => { + // const userValue = { username: '123', id: '1' }; + // mockedAxios.post.mockReturnValue(Promise.resolve({ data: userValue })); + // const action = loginByUsername({ username: 'name', password: '123' }); + // const result = await action(dispatch, getState, undefined); + // + // expect(dispatch).toHaveBeenCalledWith(userActions.setAuthData(userValue)); + // expect(dispatch).toHaveBeenCalledTimes(3); + // expect(mockedAxios.post).toHaveBeenCalled(); + // expect(result.meta.requestStatus).toBe('fulfilled'); + // expect(result.payload).toEqual(userValue); + // }); + // + // test('loginByUsername async error thunk test', async () => { + // mockedAxios.post.mockReturnValue(Promise.resolve({ status: 403 })); + // const action = loginByUsername({ username: 'name', password: '123' }); + // const result = await action(dispatch, getState, undefined); + // + // expect(dispatch).toHaveBeenCalledTimes(2); + // expect(mockedAxios.post).toHaveBeenCalled(); + // expect(result.meta.requestStatus).toBe('rejected'); + // expect(result.payload).toBe('error'); + // }); + + test('loginByUsername async thunk test', async () => { + const userValue = { username: '123', id: '1' }; + mockedAxios.post.mockReturnValue(Promise.resolve({ data: userValue })); + + const thunk = new TestAsyncThunk(loginByUsername); + const result = await thunk.callThunk({ username: '123', password: '123' }); + + expect(thunk.dispatch).toHaveBeenCalledWith(userActions.setAuthData(userValue)); + expect(thunk.dispatch).toHaveBeenCalledTimes(3); + expect(mockedAxios.post).toHaveBeenCalled(); + expect(result.meta.requestStatus).toBe('fulfilled'); + expect(result.payload).toEqual(userValue); + }); + + test('loginByUsername async error thunk test', async () => { + mockedAxios.post.mockReturnValue(Promise.resolve({ status: 403 })); + + const thunk = new TestAsyncThunk(loginByUsername); + const result = await thunk.callThunk({ username: '123', password: '123' }); + + expect(thunk.dispatch).toHaveBeenCalledTimes(2); + expect(mockedAxios.post).toHaveBeenCalled(); + expect(result.meta.requestStatus).toBe('rejected'); + expect(result.payload).toBe('error'); + }); +}); diff --git a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts index 7bf7bcc..0b0b6c4 100644 --- a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts +++ b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts @@ -8,11 +8,6 @@ interface LoginByUsernameProps { password: string } -enum LoginErrors { - INCORRECT_DATA = '', - SERVER_ERROR = '' -} - export const loginByUsername = createAsyncThunk< User, LoginByUsernameProps, @@ -33,6 +28,6 @@ export const loginByUsername = createAsyncThunk< return response.data; } catch (error) { console.log(error); - return thunkAPI.rejectWithValue('login_error'); + return thunkAPI.rejectWithValue('error'); } }); diff --git a/src/features/AuthByUsername/model/slice/loginSlice.test.ts b/src/features/AuthByUsername/model/slice/loginSlice.test.ts new file mode 100644 index 0000000..55b94d2 --- /dev/null +++ b/src/features/AuthByUsername/model/slice/loginSlice.test.ts @@ -0,0 +1,21 @@ +import { DeepPartial } from '@reduxjs/toolkit'; +import { LoginSchema } from '../types/loginSchema'; +import { loginActions, loginReducer } from './loginSlice'; + +describe('loginSlice.test', () => { + test('test set username', () => { + const state: DeepPartial = { username: '123' }; + expect(loginReducer( + state as LoginSchema, + loginActions.setUsername('123123'), + )).toEqual({ username: '123123' }); + }); + + test('test set password', () => { + const state: DeepPartial = { password: '123' }; + expect(loginReducer( + state as LoginSchema, + loginActions.setPassword('123123'), + )).toEqual({ password: '123123' }); + }); +}); diff --git a/src/shared/lib/tests/TestAsyncThunk/TestAsyncThunk.ts b/src/shared/lib/tests/TestAsyncThunk/TestAsyncThunk.ts new file mode 100644 index 0000000..14e9c46 --- /dev/null +++ b/src/shared/lib/tests/TestAsyncThunk/TestAsyncThunk.ts @@ -0,0 +1,26 @@ +import { StateSchema } from 'app/providers/StoreProvider'; +import { AsyncThunkAction } from '@reduxjs/toolkit'; + +type ActionCreatorType += (arg: Arg) => AsyncThunkAction; + +export class TestAsyncThunk { + dispatch: jest.MockedFn; + + getState: () => StateSchema; + + actionCreator: ActionCreatorType; + + constructor(actionCreator: ActionCreatorType) { + this.actionCreator = actionCreator; + this.dispatch = jest.fn(); + this.getState = jest.fn(); + } + + async callThunk(arg: Arg) { + const action = this.actionCreator(arg); + const result = await action(this.dispatch, this.getState, undefined); + + return result; + } +} diff --git a/src/shared/ui/Button/Button.test.tsx b/src/shared/ui/Button/Button.test.tsx index 3f962bc..134c10b 100644 --- a/src/shared/ui/Button/Button.test.tsx +++ b/src/shared/ui/Button/Button.test.tsx @@ -10,6 +10,6 @@ describe('Button', () => { test('Test clear theme', () => { render(); expect(screen.getByText('TEST')).toHaveClass('clear'); - screen.debug(); + // screen.debug(); }); });