diff --git a/README.md b/README.md index c59df34..5640ada 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# testing-app +# Testing App + Demo for react native testing library + +Please don't judge me for coding practices using this code base. I am better than this; might be. diff --git a/app/screens/LoginScreen/EmailPasswordForm/tests/index.test.tsx b/app/screens/LoginScreen/EmailPasswordForm/tests/index.test.tsx index 9406619..bcb9020 100644 --- a/app/screens/LoginScreen/EmailPasswordForm/tests/index.test.tsx +++ b/app/screens/LoginScreen/EmailPasswordForm/tests/index.test.tsx @@ -12,35 +12,53 @@ import { describe('', () => { it('Expect to show password required', async () => { + // Dummy email value const email = 'email@email.com'; + // Grabbing our parent component const { getByTestId } = render( null} onForgotPasswordPress={() => null} />, ); + + // Grabbing our input & button components const emailInput = getByTestId(TEST_ID_EMAIL_INPUT); const button = getByTestId(TEST_ID_SUBMIT_BUTTON); + /** + * We are changing text in inputs here; that requires a state change + * Since setState is async in react we have to execute this tests + * in async way. RNTL give waitFor API for this. + */ await waitFor(() => { fireEvent.changeText(emailInput, email); + // Just making sure that value is updated in input expect(emailInput.props.value).toBe(email); fireEvent.press(button); + + // We have passwordInput_ERROR component that only renders when error is there expect(getByTestId('passwordInput_ERROR')).toBeDefined(); }); }); it('Expect to call handle submit with email & password', async () => { + // Dummy inputs const email = 'email@email.com'; const password = 'qwerty1234'; + + // Expected output const expectedOutput = { email, password, }; let output = {}; + + // Mock onSubmit method that we are expecting will be executed const onSubmit = jest.fn((data) => (output = data)); + // Rendering our component & grabbing required nodes. const { getByTestId } = render( ', () => { const emailInput = getByTestId(TEST_ID_EMAIL_INPUT); const passwordInput = getByTestId(TEST_ID_PASSWORD_INPUT); + // Testing behaviors await waitFor(() => { fireEvent.changeText(emailInput, email); expect(emailInput.props.value).toBe(email); @@ -58,6 +77,10 @@ describe('', () => { fireEvent.changeText(passwordInput, password); expect(passwordInput.props.value).toBe(password); + /** + * Here we are asserting that onSubmit is not just called + * but it is called with expected output. + */ fireEvent.press(button); expect(onSubmit).toHaveBeenCalledTimes(1); expect(output).toEqual(expectedOutput); diff --git a/app/screens/LoginScreen/tests/index.test.tsx b/app/screens/LoginScreen/tests/index.test.tsx index 6b535b4..6222501 100644 --- a/app/screens/LoginScreen/tests/index.test.tsx +++ b/app/screens/LoginScreen/tests/index.test.tsx @@ -20,28 +20,32 @@ import { } from '../constants'; describe('Login Screen', () => { - afterEach(() => { - fetchMock.reset(); - fetchMock.restore(); - }); - - it('Expect to save token in AsyncStorage & navigate to home screen on successful login', async () => { + // Setting up fetch mock before execution of any test + beforeAll(() => { const endPoint = `${API_URL}${LOGIN_ENDPOINT}`; fetchMock.post(endPoint, { status: 200, body: JSON.stringify(LOGIN_EXPECTED_RESPONSE), }); + }); + // Testing complete flow + it('Expect to save token in AsyncStorage & navigate to home screen on successful login', async () => { + // Mocking navigate method const navigate = jest.fn(); + const endPoint = `${API_URL}${LOGIN_ENDPOINT}`; + // Dummy data to supply to form const email = 'email@email.com'; const password = 'password'; + // Getting element const screen = render(); const emailInput = screen.getByTestId(TEST_ID_EMAIL_INPUT); const passwordInput = screen.getByTestId(TEST_ID_PASSWORD_INPUT); const button = screen.getByTestId(TEST_ID_SUBMIT_BUTTON); + // Formik requires all state changes to be wrapper in async method. await waitFor(() => { fireEvent.changeText(emailInput, email); fireEvent.changeText(passwordInput, password); @@ -51,16 +55,27 @@ describe('Login Screen', () => { fireEvent.press(button); }); + // Asserting that API has been called expect(fetchMock).toHaveBeenCalledWith(endPoint, { body: `{"email":"${email}","password":"${password}"}`, headers: { 'Content-Type': 'application/json' }, method: 'POST', }); + + // Asserting screen navigation expect(navigate).toHaveBeenCalledTimes(1); expect(navigate).toHaveBeenCalledWith(HOME, {}); + + // Asserting token storage in local storage. expect(AsyncStorage.setItem).toHaveBeenCalledWith( AUTH_TOKEN_KEY, LOGIN_EXPECTED_RESPONSE.data.tokens.jwtToken, ); }); + + // Cleaning up fetch mock + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); }); diff --git a/app/theme/Button/index.tsx b/app/theme/Button/index.tsx index bac0ddd..460f407 100644 --- a/app/theme/Button/index.tsx +++ b/app/theme/Button/index.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useRef } from 'react'; import { Animated } from 'react-native'; import Text from 'theme/Text'; -import TouchFeedback from 'theme/TouchFeedback'; +import TouchFeedback, { TouchFeedbackProps } from 'theme/TouchFeedback'; import style from './style'; @@ -22,7 +22,7 @@ const typeForeground = { tertiary: style.tertiaryForeground, }; -interface ButtonProps { +interface ButtonProps extends TouchFeedbackProps { onPress: (...args: any[]) => any; label: string | React.ReactNode; mini?: boolean; diff --git a/app/theme/Button/tests/index.test.tsx b/app/theme/Button/tests/index.test.tsx index c805f6d..ff3f443 100644 --- a/app/theme/Button/tests/index.test.tsx +++ b/app/theme/Button/tests/index.test.tsx @@ -5,15 +5,31 @@ import { fireEvent } from '@testing-library/react-native'; import { render } from 'utils/testWrapper'; import Button from '../index'; +// Describing a test suite describe('