Skip to content

Commit

Permalink
Update Unit testing and add to CI (#58)
Browse files Browse the repository at this point in the history
* chore: gitignore coverage reports

* chore: update jest test script

* refactor: minimize error logs

* test: update thorough user controller testing

* chore: update tsconfig type files

* test: add auth middleware tests

* test: add poll tests

* test: improve test coverage

* ci: add server unit testing to ci

* test: update frontend unit tests

* ci: add frontend unit tests to ci
  • Loading branch information
tristenwallace committed Jul 22, 2024
1 parent dcfd215 commit cd5f8f6
Show file tree
Hide file tree
Showing 19 changed files with 705 additions and 369 deletions.
8 changes: 7 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ jobs:
- run:
name: Lint
command: |
npm run lint
npm run lint
- run:
name: Run Tests
command: |
npm run test-server
npm run test-client
echo "Tests completed successfully."
- run:
name: Front-End Build
command: |
Expand Down
28 changes: 13 additions & 15 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
server/node_modules
frontend/node_modules
/server/node_modules
/frontend/node_modules
/.pnp
.pnp.js

# testing
/coverage
**/coverage/

# production
frontend/build
server/dist
server/dist.zip
/frontend/build
/server/dist
/server/dist.zip

# misc
.DS_Store
.env
.env.development
.env.test
.env.production
# environment variables
.env*
cypress.env.json
.secrets

# misc
.DS_Store

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn-error.log*
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"start": "react-scripts start --project ./frontend",
"build": "react-scripts build --project ./frontend",
"build-production": "REACT_APP_API_URL=https://employee-polling.us-east-2.elasticbeanstalk.com react-scripts build --project ./frontend",
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!@toolz/allow-react)/\" --env=jsdom",
"test": "react-scripts test --coverage --watchAll=false --transformIgnorePatterns \"node_modules/(?!@toolz/allow-react)/\"",
"eject": "react-scripts eject",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/features/pollSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface PollsState {
}

// Initial state for polls
const initialState: PollsState = {
export const initialState: PollsState = {
polls: {},
status: 'idle',
error: undefined,
Expand Down
98 changes: 98 additions & 0 deletions frontend/src/tests/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,102 @@ describe('Home Page', () => {
expect(screen.queryByText('Show Answered Polls')).not.toBeInTheDocument();
expect(screen.getByText('Show Unanswered Polls')).toBeInTheDocument();
});

it('displays loading spinner when polls are loading', () => {
const store = mockStore({
users: {
currentUser: {
id: '123',
name: 'User',
username: 'User',
pollsCreated: 1,
pollsVotedOn: 0,
},
users: [],
status: 'succeeded',
error: null,
},
poll: {
polls: {},
status: 'loading',
error: null,
},
});

render(
<Provider store={store}>
<Router>
<Home />
</Router>
</Provider>,
);

expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

it('displays error message when fetching polls fails', () => {
const store = mockStore({
users: {
currentUser: {
id: '123',
name: 'User',
username: 'User',
pollsCreated: 1,
pollsVotedOn: 0,
},
users: [],
status: 'succeeded',
error: null,
},
poll: {
polls: {},
status: 'failed',
error: 'Failed to fetch polls',
},
});

render(
<Provider store={store}>
<Router>
<Home />
</Router>
</Provider>,
);

expect(
screen.getByText('Error: Failed to fetch polls'),
).toBeInTheDocument();
});

it('displays no polls available message when there are no polls', () => {
const store = mockStore({
users: {
currentUser: {
id: '123',
name: 'User',
username: 'User',
pollsCreated: 1,
pollsVotedOn: 0,
},
users: [],
status: 'succeeded',
error: null,
},
poll: {
polls: {},
status: 'succeeded',
error: null,
},
});

render(
<Provider store={store}>
<Router>
<Home />
</Router>
</Provider>,
);

expect(screen.getByText('No polls available.')).toBeInTheDocument();
});
});
49 changes: 0 additions & 49 deletions frontend/src/tests/Navbar.test.tsx

This file was deleted.

74 changes: 74 additions & 0 deletions frontend/src/tests/PollList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { configureStore, Store } from '@reduxjs/toolkit';
import pollReducer, {
fetchPolls,
addNewPoll,
PollsState,
initialState,
} from '../features/pollSlice';
import * as api from '../server/api';
import { ThunkDispatch } from 'redux-thunk';
import { RootState } from '../store/store';
import { Action } from 'redux';

jest.mock('../server/api');

type AppThunkDispatch = ThunkDispatch<RootState, unknown, Action>;

const mockStore = (state: PollsState) =>
configureStore({
reducer: {
poll: pollReducer,
},
preloadedState: {
poll: state,
},
});

describe('pollSlice', () => {
let store: Store<RootState, Action> & { dispatch: AppThunkDispatch };

beforeEach(() => {
store = mockStore(initialState) as Store<RootState, Action> & {
dispatch: AppThunkDispatch;
};
jest.clearAllMocks();
});

it('should handle initial state', () => {
expect(store.getState().poll).toEqual(initialState);
});

it('should handle fetchPolls', async () => {
const polls = [
{ id: '1', optionOne: 'Option 1', optionTwo: 'Option 2', votes: [] },
];
(api.fetchPolls as jest.Mock).mockResolvedValue({ polls });

await store.dispatch(fetchPolls());

const state = store.getState().poll;
expect(state.polls['1']).toEqual(polls[0]);
expect(state.status).toEqual('succeeded');
expect(state.error).toBeUndefined();
});

it('should handle addNewPoll', async () => {
const newPoll = {
id: '2',
optionOne: 'Option 1',
optionTwo: 'Option 2',
userId: '1',
votes: [],
};
(api.createPoll as jest.Mock).mockResolvedValue(newPoll);

await store.dispatch(addNewPoll(newPoll));

const state = store.getState().poll;
expect(state.polls['2']).toEqual(newPoll);
expect(state.status).toEqual('succeeded');
expect(state.error).toBeUndefined();
});

// Add more tests for voteOnPoll
});
48 changes: 48 additions & 0 deletions frontend/src/tests/createPollForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
import CreatePollForm from '../components/CreatePollForm';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { BrowserRouter as Router } from 'react-router-dom';

const mockStore = configureStore();

describe('CreatePollForm', () => {
let store: ReturnType<typeof mockStore>;

beforeEach(() => {
store = mockStore({
users: {
currentUser: {
id: '123',
name: 'User',
username: 'User',
pollCount: 1,
voteCount: 0,
},
users: [],
status: 'succeeded',
error: undefined,
},
poll: {
polls: {},
status: 'idle',
error: undefined,
},
});
store.dispatch = jest.fn();
});

it('renders form inputs', async () => {
render(
<Provider store={store}>
<Router>
<CreatePollForm />
</Router>
</Provider>,
);

expect(screen.getByLabelText(/Option One/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Option Two/i)).toBeInTheDocument();
expect(screen.getByText(/Create Poll/i)).toBeInTheDocument();
});
});
Loading

0 comments on commit cd5f8f6

Please sign in to comment.