> For the complete documentation index, see [llms.txt](https://soufatn.gitbook.io/guide-ngrx/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://soufatn.gitbook.io/guide-ngrx/step-14.md).

# Advanced testing

## Mocks

Concernant les tests, voici les valeurs pour traiter les différents cas :

*store/mock-value.ts*

```typescript
import { Todo } from '@Models/todo';
import * as fromReducer from '@Reducers/todo-list.reducer';

export const { initialState } = fromReducer;

export const arrayOfTodos: Todo[] = [
    { id: 1, userId: 2, title: 'for testing 1', completed: true },
    { id: 2, userId: 5, title: 'for testing 2', completed: false },
    { id: 3, userId: 4, title: 'for testing 3', completed: false },
    { id: 4, userId: 9, title: 'for testing 4', completed: true }
];

export const stateWithData = {
    ...initialState,
    ids: [1, 2, 3, 4],
    loaded: true,
    entities: {
        1 : arrayOfTodos[0],
        2 : arrayOfTodos[1],
        3 : arrayOfTodos[2],
        4 : arrayOfTodos[3],
    }
};


export const singleTodo: Todo = { id: 2, userId: 5, title: 'for testing 2', completed: false };
```

## Actions

Tester nos actions reste relativement simple.\
Créer une instance de l'action et vérifier le type ainsi que le *payload* si besoin :

```typescript
const action = new TodoListModule.ErrorLoadAction(message);
// Attention dans le expect 
expect({...action}). // GOOD
expect(action). // BAD
```

*todo-list.actions.spec.ts*

```typescript
import { Todo } from '@Models/todo';

import { TodoListModule } from './todo-list.action';

describe('Todos actions', () => {

    describe('Error actions', () => {
        describe('ErrorLoadAction', () => {
            it('should create an action', () => {
                const message = { message: 'for testing' };
                const action = new TodoListModule.ErrorLoadAction(message);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.ERROR_LOAD_ACTION,
                    payload: message
                });
            });
        });
    });

    describe('Init Todos Actions', () => {

        describe('LoadInitTodos', () => {
            it('should create an action', () => {
                const action = new TodoListModule.LoadInitTodos();
                // Avoid error message { ...action }
                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.LOAD_INIT_TODOS
                });
            });
        });

        describe('SuccessInitTodos', () => {
            it('should create an action', () => {
                const payload: Todo[] = [
                    { id: 1, userId: 1, title: 'for testing', completed: true }
                ];
                const action = new TodoListModule.SuccessInitTodos(payload);
                // Avoid error message { ...action }
                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.SUCCESS_INIT_TODOS,
                    payload: payload
                });
            });
        });

    });
    describe('Create Todos Actions', () => {

        describe('LoadCreateTodo', () => {
            it('should create an action', () => {

                const payload: Todo = { id: 1, userId: 1, title: 'for testing', completed: true };
                const action = new TodoListModule.LoadCreateTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.LOAD_CREATE_TODO,
                    payload: payload
                });
            });
        });

        describe('SuccessCreateTodo', () => {
            it('should create an action', () => {

                const payload: Todo = { id: 1, userId: 1, title: 'for testing', completed: true };
                const action = new TodoListModule.SuccessCreateTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.SUCCESS_CREATE_TODO,
                    payload: payload
                });
            });
        });

    });

    describe('Select Todo Action', () => {

        describe('SelectTodo', () => {
            it('should create an action', () => {

                const payload: Todo = { id: 1, userId: 1, title: 'for testing', completed: true };
                const action = new TodoListModule.SelectTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.SELECT_TODO,
                    payload: payload
                });
            });
        });

    });

    describe('Update Todo Actions', () => {

        describe('LoadUpdateTodo', () => {
            it('should create an action', () => {

                const payload: Todo = { id: 1, userId: 1, title: 'for testing', completed: true };
                const action = new TodoListModule.LoadUpdateTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.LOAD_UPDATE_TODO,
                    payload: payload
                });
            });
        });

        describe('SuccessUpdateTodo', () => {
            it('should create an action', () => {

                const payload: Todo = { id: 1, userId: 1, title: 'for testing', completed: true };
                const action = new TodoListModule.SuccessUpdateTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.SUCCESS_UPDATE_TODO,
                    payload: payload
                });
            });
        });

    });

    describe('Delete Todo Actions', () => {

        describe('LoadDeleteTodo', () => {
            it('should create an action', () => {

                const payload = 1;
                const action = new TodoListModule.LoadDeleteTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.LOAD_DELETE_TODO,
                    payload: payload
                });
            });
        });

        describe('SuccessUpdateTodo', () => {
            it('should create an action', () => {

                const payload = 1;
                const action = new TodoListModule.SuccessDeleteTodo(payload);

                expect({...action}).toEqual({
                    type: TodoListModule.ActionTypes.SUCCESS_DELETE_TODO,
                    payload: payload
                });
            });
        });

    });
});
```

## Reducers

Concernant les tests de *reducer*, c'est également très simple :

*todo-list.reducer.spec.ts*

```typescript
import { TodoListModule } from '@Actions/todo-list.action';

import { arrayOfTodos, initialState, singleTodo, stateWithData } from '../mock-value';
import * as FromReducer from './todo-list.reducer';

describe('Todos reducer', () => {

    // Default
    describe('undefined action', () => {
        it('should return the default state', () => {
            const action: any = {} ;
            const state = FromReducer.todosReducer(undefined, action);

            expect(state).toBe(initialState);
        });
    });

    // Init Todos
    describe('LoadInitTodos action', () => {
        it('should set loading to true', () => {

            const action: TodoListModule.Actions = new TodoListModule.LoadInitTodos();
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.loading).toEqual(true);
            expect(state.entities).toEqual({});
        });
    });

    describe('SuccessInitTodos action', () => {
        it('should map an array to entities', () => {
            const entities = {
                1 : arrayOfTodos[0],
                2 : arrayOfTodos[1],
                3 : arrayOfTodos[2],
                4 : arrayOfTodos[3],
            };

            const action: TodoListModule.Actions =
                new TodoListModule.SuccessInitTodos(arrayOfTodos);
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.loading).toEqual(false);
            expect(state.loaded).toEqual(true);
            expect(state.entities).toEqual(entities);
        });
    });

    // Create Todo
    describe('LoadCreateTodo action', () => {
        it('should set loading to true', () => {

            const action: TodoListModule.Actions = new TodoListModule.LoadCreateTodo(singleTodo);
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.loading).toEqual(true);
            expect(state.entities).toEqual({});
        });
    });

    describe('SuccessCreateTodo action', () => {
        it('should add an entitie', () => {

            const action: TodoListModule.Actions = new TodoListModule.SuccessCreateTodo(singleTodo);
            const state = FromReducer.todosReducer(initialState, {...action});

            const entities = {
                2 : arrayOfTodos[1]
            };

            expect(state.loading).toEqual(false);
            expect(state.entities).toEqual(entities);
        });
    });

    // Select Todo
    describe('SelectTodo action', () => {
        it('should set selectedTodo with action payload', () => {

            const action: TodoListModule.Actions = new TodoListModule.SelectTodo(singleTodo);
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.selectedTodo).toEqual(singleTodo);
        });
    });

    // Update Todo
    describe('LoadUpdateTodo action', () => {
        it('should set loading to true', () => {

            const action: TodoListModule.Actions = new TodoListModule.LoadUpdateTodo(singleTodo);
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.loading).toEqual(true);
        });
    });

    describe('SuccessUpdateTodo action', () => {
        it('should patch an entities object with the action payload', () => {

            const updateValue = { id: 4, userId: 9, title: 'nouvelle valeur', completed: true };

            const action: TodoListModule.Actions =
                new TodoListModule.SuccessUpdateTodo(updateValue);

            const entities = {
                1 : arrayOfTodos[0],
                2 : arrayOfTodos[1],
                3 : arrayOfTodos[2],
                4 : updateValue,
            };

            const state = FromReducer.todosReducer(stateWithData, {...action});
            expect(state.loading).toEqual(false);
            expect(state.entities).toEqual(entities);
        });
    });

    // Delete Todo
    describe('LoadUpdateTodo action', () => {
        it('should set loading to true', () => {
            const id = 2;
            const action: TodoListModule.Actions = new TodoListModule.LoadDeleteTodo(id);
            const state = FromReducer.todosReducer(initialState, {...action});

            expect(state.loading).toEqual(true);
        });
    });

    describe('SuccessCreateTodo action', () => {
        it('should remove one todo', () => {
            const id = 2;
            const action: TodoListModule.Actions =
                new TodoListModule.SuccessDeleteTodo(id);
            const state = FromReducer.todosReducer(stateWithData, {...action});

            const entities = {
                1 : arrayOfTodos[0],
                3 : arrayOfTodos[2],
                4 : arrayOfTodos[3]
            };

            expect(state.entities).toEqual(entities);
            expect(state.loading).toEqual(false);
        });
    });
});
```

## Selectors

Afin de tester les **selectors**, créer une instance de store accessible depuis le test dans le **beforeEach** .\
Cela nous permet de récupérer le *state* par défaut du *selector* pour ensuite déclencher une action dans le test afin de vérifier la valeur du *selector* après une action x.

*todo-list.selector.spec.ts*

```typescript
import { TodoListModule } from '@Actions/todo-list.action';
import { TestBed } from '@angular/core/testing';
import { Store, StoreModule } from '@ngrx/store';
import { AppState, reducers } from '@StoreConfig';

import { arrayOfTodos, initialState, singleTodo, stateWithData } from '../mock-value';
import * as selectors from './todo-list.selector';

describe('Todo selectors', () => {
    let store: Store<AppState>;
    const todos = [];
    const entities = {};

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [
                StoreModule.forRoot(reducers)
            ]
        });
        store = TestBed.get(Store);
        spyOn(store, 'dispatch').and.callThrough();
    });

    describe('selectTodoListState$', () => {
        it('it should return todos state', () => {
            let result;

            store
                .select(selectors.selectTodoListState$)
                .subscribe(value => {
                    result = value;
                });

            expect(result).toEqual(initialState);

            store.dispatch(new TodoListModule.SuccessInitTodos(arrayOfTodos));

            expect(result).toEqual(stateWithData);
        });
    });

    describe('selectTodoListEntitiesConverted$', () => {
        it('it should return todos entities converted', () => {
            let result;

            store
                .select(selectors.selectTodoListEntitiesConverted$)
                .subscribe(value => {
                    result = value;
                });

            expect(result).toEqual([]);

            store.dispatch(new TodoListModule.SuccessInitTodos(arrayOfTodos));

            expect(result).toEqual(arrayOfTodos);
        });
    });

    describe('selectTodoSelected$', () => {
        it('it should return selectedTodo', () => {
            let result;

            store
                .select(selectors.selectTodoSelected$)
                .subscribe(value => {
                    result = value;
                });

            expect(result).toEqual(undefined);

            store.dispatch(new TodoListModule.SelectTodo(singleTodo));

            expect(result).toEqual(singleTodo);
        });
    });

    describe('selectTodosLoaded$', () => {
        it('it should return loaded props of todos', () => {
            let result;

            store
                .select(selectors.selectTodosLoaded$)
                .subscribe(value => {
                    result = value;
                });

            expect(result).toEqual(false);

            store.dispatch(new TodoListModule.SuccessInitTodos(arrayOfTodos));

            expect(result).toEqual(true);
        });
    });

    describe('selectTodosErrors$', () => {
        it('it should return logs props of logs todos', () => {
            let result;

            store
                .select(selectors.selectTodosErrors$)
                .subscribe(value => {
                    result = value;
                });

            expect(result).toEqual(undefined);

            store.dispatch(new TodoListModule.SuccessCreateTodo(singleTodo));

            expect(result).toEqual(
                { type: 'SUCCESS', message: 'La todo à été crée avec succès' }
            );
        });
    });


});
```

## Effects

Pour tester les *effects*, utiliser [**jasmine-marbles**](https://github.com/synapse-wireless-labs/jasmine-marbles).

*todo-list.effect.spec.ts*

```typescript
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { Actions } from '@ngrx/effects';
import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
import { of } from 'rxjs/observable/of';

import { TodoListService } from '../../services/todo-list.service';
import * as fromActions from '../actions/todo-list.action';
import * as fromEffects from '../effects/todo-list.effect';
import { arrayOfTodos, singleTodo } from '../mock-value';

export class TestActions extends Actions {
    constructor () {
        super(empty());
    }
    set stream(source: Observable<any>) {
        this.source = source;
    }
}

export function getActions() {
    return new TestActions();
}


describe('Testing Effects', () => {
    let actions$: TestActions;
    let service: TodoListService;
    let effects: fromEffects.TodoListEffects;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [
                HttpClientTestingModule
            ],
            providers: [
                TodoListService,
                fromEffects.TodoListEffects,
                { provide: Actions, useFactory: getActions }
            ]
        });
        actions$ = TestBed.get(Actions);
        service = TestBed.get(TodoListService);
        effects = TestBed.get(fromEffects.TodoListEffects);

        spyOn(service, 'getTodos').and.returnValue(of(arrayOfTodos));
        spyOn(service, 'createTodo').and.returnValue(of(singleTodo));
        spyOn(service, 'deleteTodo').and.returnValue(of(singleTodo.id));
        spyOn(service, 'patchTodo').and.returnValue(of(singleTodo));
    });

    describe('LoadTodos$', () => {
        it('should return a collection of todos', () => {
            const action = new fromActions.TodoListModule.LoadInitTodos();
            const completion = new fromActions.TodoListModule.SuccessInitTodos(arrayOfTodos);

            actions$.stream = hot('-a', { a: action });
            const expected = cold('-b', { b: completion });

            expect(effects.LoadTodos$).toBeObservable(expected);

        });
    });

    describe('LoadCreateTodo$', () => {
        it('should return a todo item created', () => {
            const action = new fromActions.TodoListModule.LoadCreateTodo(singleTodo);
            const completion = new fromActions.TodoListModule.SuccessCreateTodo(singleTodo);

            actions$.stream = hot('-a', { a: action });
            const expected = cold('-b', { b: completion });

            expect(effects.LoadCreateTodo$).toBeObservable(expected);

        });
    });

    describe('LoadDeleteTodo$', () => {
        it('should return a id of a todo suppressed', () => {
            const action = new fromActions.TodoListModule.LoadDeleteTodo(singleTodo.id);
            const completion = new fromActions.TodoListModule.SuccessDeleteTodo(singleTodo.id);

            actions$.stream = hot('-a', { a: action });
            const expected = cold('-b', { b: completion });

            expect(effects.LoadDeleteTodo$).toBeObservable(expected);

        });
    });

    describe('LoadUpdateTodo$', () => {
        it('should return a todo patched', () => {
            const action = new fromActions.TodoListModule.LoadUpdateTodo(singleTodo);
            const completion = new fromActions.TodoListModule.SuccessUpdateTodo(singleTodo);

            actions$.stream = hot('-a', { a: action });
            const expected = cold('-b', { b: completion });

            expect(effects.LoadUpdateTodo$).toBeObservable(expected);

        });
    });

});
```

La couverture de nos tests est maintenant optimisée à 100%.

### [Sommaire >>](https://github.com/soufatn/guide-ngrx/tree/e1a2c90ecc113076b64486a639dbfe6629cc2c7a/sommaire.md)
