Skip to content

Client Side Testing

JT edited this page Nov 26, 2016 · 16 revisions

Quick Overview of Client Side Testing

Our Implementation of Client Side Testing

Technologies used

Why we chose them

  • Jasmine: We chose Jasmine as our testing framework due to its simple syntax, and the fact that it is 100% standalone.

On a side-note Jasmine is a requirement to test the Angular2 framework because of the lack of hook libraries for other testing frameworks. Instead of adhearing to a differrnt testing framework for the server-side we believed it would be simpler to use Jasmine for all layers of the GOAT-stack.

  • Karma: We chose Karma because it was made by Google and chosen by Google as a test-runner for Angular 2.

Because of our implementation of Redux for Angular2 only two files are responsible for client-side data handling, 'actions' and 'reducers'. Components should only be responsible for DOM event manipulation and/or hooks for the Redux store data to their respective templates. Which means Protractors e2e testing should be responsible for the rest.

  • Actions:
    • Primary assumptions to test for:
      • is an action being called.
      • was the action that was called the correct one.
  • Reducers:
    • Primary assumptions to test for:
      • Is the initial state correct
      • Is the previous state correct
      • Is the action correct
      • Is the next state correct

Examples

actions.ts

import { Injectable } from '@angular/core';
import { FormGroup, NgForm } from '@angular/forms';

import { NgRedux } from 'ng2-redux';
import { IAppState } from '../../store/index';

/////////////////////////////////////////////////////////////////////////
/* UserForm Actions: used to call dispatches to change the userForm
                     object in the store
  
    LOGIN_FORM_IN       ->   Opens the login form
    LOGIN_FORM_OUT      ->   Closes the Login form
*/
/////////////////////////////////////////////////////////////////////////
@Injectable()
export class UserFormActions {

  constructor(private ngRedux: NgRedux<IAppState>) { }

  static LOGIN_FORM_IN: string = 'LOGIN_FORM_IN';
  static LOGIN_FORM_OUT: string = 'LOGIN_FORM_OUT';

  loginForm(action: boolean) {
    if (action)
      this.ngRedux.dispatch({ type: UserFormActions.LOGIN_FORM_IN });
    else
      this.ngRedux.dispatch({ type: UserFormActions.LOGIN_FORM_OUT });
  }
}

actions.spec.ts

import { NgRedux } from 'ng2-redux';
import { UserFormActions } from './userForm.actions';

class MockRedux extends NgRedux<any> {
  constructor() {
    super(null);
  }
  dispatch: () => {};
}

describe('UserForm Actions Creator', () => {
  let actions: UserFormActions;
  let mockRedux: NgRedux<any>;

  beforeEach(() => {
    mockRedux = new MockRedux();
    actions = new UserFormActions(mockRedux);
  });

  it('should dispatch LOGIN_FORM_IN action', () => {
    const expectedAction = {
      type: UserFormActions.LOGIN_FORM_IN
    };

    spyOn(mockRedux, 'dispatch');
    actions.loginForm(true);

    expect(mockRedux.dispatch).toHaveBeenCalled();
    expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
  });

  it('should dispatch LOGIN_FORM_OUT action', () => {
    const expectedAction = {
      type: UserFormActions.LOGIN_FORM_OUT
    };

    spyOn(mockRedux, 'dispatch');
    actions.loginForm(false);

    expect(mockRedux.dispatch).toHaveBeenCalled();
    expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
  });
});

reducer.ts

  • click here to see the transformers file
  • click here to see the types file
  • click here to see the initial-state file
import { UserFormActions } from '../../actions/userForm/userForm.actions';
import { reimmutifyUserForm } from './userForm.transformers';
import { IUserForm } from './userForm.types';
import { INITIAL_STATE } from './userForm.initial-state';

// Define the reducer that will initiate state changes for userForm
export function userFormReducer(state: IUserForm = INITIAL_STATE, action: any) {
  // will decide what state change is necessary based off the type
  switch (action.type) {
    case UserFormActions.LOGIN_FORM_IN:
      return state
        .updateIn(['userSigning'], val => true)
        .updateIn(['userSignup'], val => false);
    case UserFormActions.LOGIN_FORM_OUT:
      return state
        .updateIn(['userSignup'], val => false)
        .updateIn(['userSigning'], val => false);
    default:
      return state;
  }
}

reducer.spec.ts

import { Map } from 'immutable';
import { userFormReducer } from './userForm.reducer';
import { INITIAL_STATE } from './userForm.initial-state';
import { UserFormActions } from '../../actions/userForm/userForm.actions';

describe('UserForm Reducer', () => {
  let initialState = INITIAL_STATE;

  beforeEach(() => {
    initialState = userFormReducer(undefined, { type: 'TEST_INIT' });
  });

  it('should have an immutable initial state', () => {
    expect(Map.isMap(initialState)).toBe(true);
  });

  it('should set userSigning to true on LOGIN_FORM_IN', () => {
    const previousState = initialState;
    const nextState = userFormReducer(previousState,
      { type: UserFormActions.LOGIN_FORM_IN });

    expect(previousState.getIn(['userSigning'])).toBe(false);
    expect(previousState.getIn(['userSignup'])).toBe(false);

    expect(nextState.getIn(['userSigning'])).toBe(true);
    expect(nextState.getIn(['userSignup'])).toBe(false);
  });

  it('should set userSigning to false on LOGIN_FORM_OUT', () => {
    const previousState = userFormReducer(initialState,
      { type: UserFormActions.LOGIN_FORM_IN });
    const nextState = userFormReducer(previousState,
      { type: UserFormActions.LOGIN_FORM_OUT });

    expect(previousState.getIn(['userSigning'])).toBe(true);
    expect(previousState.getIn(['userSignup'])).toBe(false);

    expect(nextState.getIn(['userSigning'])).toBe(false);
    expect(nextState.getIn(['userSignup'])).toBe(false);
  });

  it('should set swap userSignup and userSigning on LOGIN_FORM_IN', () => {
    const previousState = userFormReducer(initialState,
      { type: UserFormActions.REGISTER_FORM_IN });
    const nextState = userFormReducer(previousState,
      { type: UserFormActions.LOGIN_FORM_IN });

    expect(previousState.getIn(['userSigning'])).toBe(false);
    expect(previousState.getIn(['userSignup'])).toBe(true);

    expect(nextState.getIn(['userSigning'])).toBe(true);
    expect(nextState.getIn(['userSignup'])).toBe(false);
  });
});