I have a React project that has the following structure:
api/auth.js:
import BaseApi from './base-api';
class Authorization extends BaseApi {
namespace() {
return '/api/v1';
}
* login(username, password) {
return yield this.apiFetch('/account/login', {
method: 'POST',
body: JSON.stringify({
grant_type: 'password',
username,
password,
}),
});
}
}
export default Authorization;
Where the apiFetch
function calls the fetch
function of JS to perform the API call.
sagas/auth.js:
import { apply, put } from 'redux-saga/effects';
import {
loginFailed,
loginSucceeded,
} from '../reducers/auth.reducers';
export function* login(authApi, { payload }) {
try {
const creds = payload;
const response = yield apply(
authApi,
authApi.login,
[creds.username, creds.password]
);
if (response.errorCode) {
yield put(loginFailed({
message: response.message || 'Failed to login',
}));
} else {
yield put(loginSucceeded({
user: response.user,
refreshToken: response.refresh_token,
accessToken: response.access_token,
expiresIn: response.expires_in,
}));
}
} catch (e) {
console.error('Failed to login', e);
yield put(loginFailed({
message: 'Failed to login',
}));
}
}
sagas/index.js:
import { takeEvery } from 'redux-saga/effects';
import {
login as loginAction,
} from '../reducers/auth.reducers';
import {
login,
} from './auth.sagas';
import Authorization from '../api/authorization';
export default function* rootSaga() {
let authApi = new Authorization(config.apiHost);
yield [
// auth
takeEvery(loginAction, login, authApi),
];
}
reducers/auth.js:
import { createAction, createReducer } from 'redux-act';
import Immutable from 'seamless-immutable';
import moment from 'moment';
const INITIAL_STATE = Immutable({
user: {},
refreshToken: '',
accessToken: '',
expiresIn: 0,
loggedIn: false,
loading: false,
errorMessage: null,
});
export const login = createAction("LOGIN");
export const loginSucceeded = createAction("LOGIN_SUCCEEDED");
export const loginFailed = createAction("LOGIN_FAILED");
const reducers = createReducer({
[login]: state => state.merge({
errorMessage: null,
loading: true,
}),
[loginSucceeded]: (state, { user, refreshToken, accessToken, expiresIn }) => state.merge({
user,
loggedIn: true,
refreshToken,
accessToken,
expiresIn,
expiresAt: moment().add(expiresIn - 120, 's').toDate(),
errorMessage: null,
loading: false,
}),
[loginFailed]: (state, { message }) => state.merge({
errorMessage: message,
accessToken: null,
refreshToken: null,
expiresIn: 0,
user: {},
loading: false,
loggedIn: false,
}),
}, INITIAL_STATE);
export default reducers;
And then I have a Login
component, which has two input fields, and a button. And when the user fills the input fields and clicks on the button it performs the API call using the saga and calls the appropriate reducers depending on the success of the API call. Now, I wanted to write an integration test, to make a failing and success login. I'm using jest
with react-testing-library
. I did something like this:
login.test.js:
import React from 'react';
import {
cleanup,
fireEvent,
waitForElement,
} from '@testing-library/react';
import testUtils from '../app/utils/testUtils';
import Login from '../app/main/login';
afterEach(cleanup);
describe('Login Test', () => {
it('performs a user login', async () => {
const {
getByText,
getByPlaceholderText,
getByRole,
} = testUtils.renderWithRedux(<Login />);
getByPlaceholderText('E-Mail').value = 'myuser@gmail.com';
getByPlaceholderText('Password').value = 'mypass';
fireEvent.click(getByText('Login'));
// Incorrect login should display a snackbar.
await waitForElement(() => getByRole('alertdialog'));
});
});
Now this test fails at the last line. However, in my app, when the login is not successful a Material UI snackbar is displayed which has a role=alertdialog
attribute. The point that I understand with tests is that it actually won't perform the actual API call when the button is clicked, right? So, maybe I somehow need to mock the result or what? However, I don't know how this procedure will look like in my code structure. Any ideas how to perform such tests?
Aucun commentaire:
Enregistrer un commentaire