diff --git a/client/src/app/instance/store/effects/search.effects.spec.ts b/client/src/app/instance/store/effects/search.effects.spec.ts
index a8883613bd739aa34849aaf4b9856da775ebb89c..d7485658f0c4b0120a08b025f1bfe7bfcd40494f 100644
--- a/client/src/app/instance/store/effects/search.effects.spec.ts
+++ b/client/src/app/instance/store/effects/search.effects.spec.ts
@@ -52,7 +52,7 @@ describe('[Instance][Store] SearchEffects', () => {
                     }},
                 { provide: ToastrService, useValue: { error: jest.fn() }},
                 provideMockActions(() => actions),
-                provideMockStore({ initialState }),
+                provideMockStore({ initialState })
             ]
         }).compileComponents();
         effects = TestBed.inject(SearchEffects);
diff --git a/client/src/app/instance/store/models/svom-keyword.model.ts b/client/src/app/instance/store/models/svom-keyword.model.ts
index 1fabc699b4d1e4374f5b81f3120652615019ac65..d8ca382b519616ac5d2ce54ebec8a5867da8f6c5 100644
--- a/client/src/app/instance/store/models/svom-keyword.model.ts
+++ b/client/src/app/instance/store/models/svom-keyword.model.ts
@@ -1,6 +1,21 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface for SVOM Keywords.
+ *
+ * @interface SvomKeyword
+ */
+
 export interface SvomKeyword {
     data_type: string
     default: string
     extension: string
     name: string
-}
\ No newline at end of file
+}
diff --git a/client/src/app/instance/store/services/svom-json-kw.service.ts b/client/src/app/instance/store/services/svom-json-kw.service.ts
index 7a990aa7212a49ea99276349219e44b052d3f011..e86072e9d9d94687519559fb8ff638c5dcddc9d4 100644
--- a/client/src/app/instance/store/services/svom-json-kw.service.ts
+++ b/client/src/app/instance/store/services/svom-json-kw.service.ts
@@ -9,6 +9,8 @@
 
 import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 
 import { AppConfigService } from 'src/app/app-config.service';
@@ -22,7 +24,14 @@ import { SvomKeyword } from '../models';
 export class SvomJsonKwService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
-    loadKwSearchable(acronym: string) {
+    /**
+     * Retrieves searchable keywords for the given acronym.
+     *
+     * @param  {string} acronym - The acronym.
+     *
+     * @return Observable<SvomKeyword[]>
+     */
+    loadKwSearchable(acronym: string): Observable<SvomKeyword[]> {
         return this.http.get<{search_kw: SvomKeyword[]}[]>(`${this.config.apiUrl}/search/sp_cards?a=8&c=1::eq::${acronym}`).pipe(
             map(data => data[0].search_kw)
         );
diff --git a/client/src/app/metamodel/effects/attribute-distinct.effects.spec.ts b/client/src/app/metamodel/effects/attribute-distinct.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a1978b971248669d91b8c9fb424d467849451022
--- /dev/null
+++ b/client/src/app/metamodel/effects/attribute-distinct.effects.spec.ts
@@ -0,0 +1,88 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { AttributeDistinctEffects } from './attribute-distinct.effects';
+import { AttributeDistinctService } from '../services/attribute-distinct.service';
+import * as attributeDistinctActions from '../actions/attribute-distinct.actions';
+import * as datasetSelector from '../selectors/dataset.selector';
+import { ATTRIBUTE } from '../../../test-data';
+
+describe('[Metamodel][Effects] AttributeDistinctEffects', () => {
+    let actions = new Observable();
+    let effects: AttributeDistinctEffects;
+    let metadata: EffectsMetadata<AttributeDistinctEffects>;
+    let service: AttributeDistinctService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                AttributeDistinctEffects,
+                { provide: AttributeDistinctService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(AttributeDistinctEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(AttributeDistinctService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadAttributeDistinct$ effect', () => {
+        it('should dispatch the loadAttributeDistinctListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = attributeDistinctActions.loadAttributeDistinctList({ attribute: ATTRIBUTE });
+            const outcome = attributeDistinctActions.loadAttributeDistinctListSuccess({ values: [] });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: [] });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveAttributeDistinctList = jest.fn(() => response);
+
+            expect(effects.loadAttributeDistinct$).toBeObservable(expected);
+            expect(service.retrieveAttributeDistinctList).toHaveBeenCalledWith('myDataset', ATTRIBUTE);
+        });
+
+        it('should dispatch the loadAttributeDistinctListFail action on HTTP failure', () => {
+            const action = attributeDistinctActions.loadAttributeDistinctList({ attribute: ATTRIBUTE });
+            const error = new Error();
+            const outcome = attributeDistinctActions.loadAttributeDistinctListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveAttributeDistinctList = jest.fn(() => response);
+
+            expect(effects.loadAttributeDistinct$).toBeObservable(expected);
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/attribute-distinct.effects.ts b/client/src/app/metamodel/effects/attribute-distinct.effects.ts
index aaa51d7cdc83ba114efe06823526c7ae0395310c..6584c439e5ca4be3558606f0785413ab354a0860 100644
--- a/client/src/app/metamodel/effects/attribute-distinct.effects.ts
+++ b/client/src/app/metamodel/effects/attribute-distinct.effects.ts
@@ -17,10 +17,18 @@ import { map, mergeMap, catchError } from 'rxjs/operators';
 import * as attributeDistinctActions from '../actions/attribute-distinct.actions';
 import { AttributeDistinctService } from '../services/attribute-distinct.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+/**
+ * @class
+ * @classdesc Attribute distinct effects.
+ */
 @Injectable()
 export class AttributeDistinctEffects {
-    loadAttributeDistinct$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve distinct attribute list.
+     */
+    loadAttributeDistinct$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(attributeDistinctActions.loadAttributeDistinctList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -32,7 +40,7 @@ export class AttributeDistinctEffects {
             )
         )
     );
- 
+
     constructor(
         private actions$: Actions,
         private attributeDistinctService: AttributeDistinctService,
diff --git a/client/src/app/metamodel/effects/attribute.effects.spec.ts b/client/src/app/metamodel/effects/attribute.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..602d95d2b34db2e6ce7aaa4e75c2a53b065b8473
--- /dev/null
+++ b/client/src/app/metamodel/effects/attribute.effects.spec.ts
@@ -0,0 +1,289 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { ToastrService } from 'ngx-toastr';
+import { AttributeEffects } from './attribute.effects';
+import { AttributeService } from '../services/attribute.service';
+import * as attributeActions from '../actions/attribute.actions';
+import { ATTRIBUTE, ATTRIBUTE_LIST } from '../../../test-data';
+import * as datasetSelector from '../selectors/dataset.selector';
+
+describe('[Metamodel][Effects] AttributeEffects', () => {
+    let actions = new Observable();
+    let effects: AttributeEffects;
+    let metadata: EffectsMetadata<AttributeEffects>;
+    let service: AttributeService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                AttributeEffects,
+                { provide: AttributeService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(AttributeEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(AttributeService);
+        toastr = TestBed.inject(ToastrService);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+        router = TestBed.inject(Router);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadAttributes$ effect', () => {
+        it('should dispatch the loadAttributeListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = attributeActions.loadAttributeList();
+            const outcome = attributeActions.loadAttributeListSuccess({ attributes: ATTRIBUTE_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: ATTRIBUTE_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveAttributeList = jest.fn(() => response);
+
+            expect(effects.loadAttributes$).toBeObservable(expected);
+            expect(service.retrieveAttributeList).toHaveBeenCalledWith('myDataset');
+        });
+
+        it('should dispatch the loadAttributeListFail action on HTTP failure', () => {
+            const action = attributeActions.loadAttributeList();
+            const error = new Error();
+            const outcome = attributeActions.loadAttributeListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveAttributeList = jest.fn(() => response);
+
+            expect(effects.loadAttributes$).toBeObservable(expected);
+        });
+    });
+
+    describe('addAttribute$ effect', () => {
+        it('should dispatch the addAttributeSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = attributeActions.addAttribute({ attribute: ATTRIBUTE });
+            const outcome = attributeActions.addAttributeSuccess({ attribute: ATTRIBUTE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: ATTRIBUTE });
+            const expected = cold('--b', { b: outcome });
+            service.addAttribute = jest.fn(() => response);
+
+            expect(effects.addAttribute$).toBeObservable(expected);
+            expect(service.addAttribute).toHaveBeenCalledWith('myDataset', ATTRIBUTE);
+        });
+
+        it('should dispatch the addAttributeFail action on HTTP failure', () => {
+            const action = attributeActions.addAttribute({ attribute: ATTRIBUTE });
+            const error = new Error();
+            const outcome = attributeActions.addAttributeFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addAttribute = jest.fn(() => response);
+
+            expect(effects.addAttribute$).toBeObservable(expected);
+        });
+    });
+
+    describe('addAttributeSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addAttributeSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = attributeActions.addAttributeSuccess({ attribute: ATTRIBUTE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addAttributeSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Attribute successfully added',
+                'The new attribute was added into the database'
+            );
+        });
+    });
+
+    describe('addAttributeFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addAttributeFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = attributeActions.addAttributeFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addAttributeFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add attribute',
+                'The new attribute could not be added into the database'
+            );
+        });
+    });
+
+    describe('editAttribute$ effect', () => {
+        it('should dispatch the editAttributeSuccess action on success', () => {
+            const action = attributeActions.editAttribute({ attribute: ATTRIBUTE });
+            const outcome = attributeActions.editAttributeSuccess({ attribute: ATTRIBUTE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: ATTRIBUTE });
+            const expected = cold('--b', { b: outcome });
+            service.editAttribute = jest.fn(() => response);
+
+            expect(effects.editAttribute$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editAttributeFail action on HTTP failure', () => {
+            const action = attributeActions.editAttribute({ attribute: ATTRIBUTE });
+            const error = new Error();
+            const outcome = attributeActions.editAttributeFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editAttribute = jest.fn(() => response);
+
+            expect(effects.editAttribute$).toBeObservable(expected);
+        });
+    });
+
+    describe('editAttributeFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editAttributeFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = attributeActions.editAttributeFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editAttributeFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit attribute',
+                'The existing attribute could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteAttribute$ effect', () => {
+        it('should dispatch the deleteAttributeSuccess action on success', () => {
+            const action = attributeActions.deleteAttribute({ attribute: ATTRIBUTE });
+            const outcome = attributeActions.deleteAttributeSuccess({ attribute: ATTRIBUTE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: ATTRIBUTE });
+            const expected = cold('--b', { b: outcome });
+            service.deleteAttribute = jest.fn(() => response);
+
+            expect(effects.deleteAttribute$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteAttributeFail action on HTTP failure', () => {
+            const action = attributeActions.deleteAttribute({ attribute: ATTRIBUTE });
+            const error = new Error();
+            const outcome = attributeActions.deleteAttributeFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteAttribute = jest.fn(() => response);
+
+            expect(effects.deleteAttribute$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteAttributeSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteAttributeSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = attributeActions.deleteAttributeSuccess({ attribute: ATTRIBUTE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteAttributeSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Attribute successfully deleted',
+                'The existing attribute has been deleted'
+            );
+        });
+    });
+
+    describe('deleteAttributeFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteAttributeFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = attributeActions.deleteAttributeFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteAttributeFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete attribute',
+                'The existing attribute could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/attribute.effects.ts b/client/src/app/metamodel/effects/attribute.effects.ts
index 855fe3301ff2a861979aeea5d3b851e64673e473..c1198e7edf545cad34ef6da44b992b6a21a4e961 100644
--- a/client/src/app/metamodel/effects/attribute.effects.ts
+++ b/client/src/app/metamodel/effects/attribute.effects.ts
@@ -18,10 +18,19 @@ import { ToastrService } from 'ngx-toastr';
 import * as attributeActions from '../actions/attribute.actions';
 import { AttributeService } from '../services/attribute.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+
+/**
+ * @class
+ * @classdesc Attribute effects.
+ */
 @Injectable()
 export class AttributeEffects {
-    loadAttributes$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve attribute list.
+     */
+    loadAttributes$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(attributeActions.loadAttributeList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -33,8 +42,11 @@ export class AttributeEffects {
             )
         )
     );
-    
-    addAttribute$ = createEffect(() => 
+
+    /**
+     * Calls action to add an attribute.
+     */
+    addAttribute$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(attributeActions.addAttribute),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -47,23 +59,32 @@ export class AttributeEffects {
         )
     );
 
-    addAttributeSuccess$ = createEffect(() => 
+    /**
+     * Displays add attribute success notification.
+     */
+    addAttributeSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(attributeActions.addAttributeSuccess),
             tap(() => {
                 this.toastr.success('Attribute successfully added', 'The new attribute was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addAttributeFail$ = createEffect(() => 
+    /**
+     * Displays add attribute error notification.
+     */
+    addAttributeFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(attributeActions.addAttributeFail),
             tap(() => this.toastr.error('Failure to add attribute', 'The new attribute could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editAttribute$ = createEffect(() => 
+    /**
+     * Calls action to modify an attribute.
+     */
+    editAttribute$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(attributeActions.editAttribute),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -76,14 +97,20 @@ export class AttributeEffects {
         )
     );
 
-    editAttributeFail$ = createEffect(() => 
+    /**
+     * Displays edit attribute error notification.
+     */
+    editAttributeFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(attributeActions.editAttributeFail),
             tap(() => this.toastr.error('Failure to edit attribute', 'The existing attribute could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteAttribute$ = createEffect(() => 
+    /**
+     * Calls action to remove an attribute.
+     */
+    deleteAttribute$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(attributeActions.deleteAttribute),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -96,20 +123,26 @@ export class AttributeEffects {
         )
     );
 
-    deleteAttributeSuccess$ = createEffect(() => 
+    /**
+     * Displays delete attribute success notification.
+     */
+    deleteAttributeSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(attributeActions.deleteAttributeSuccess),
             tap(() => this.toastr.success('Attribute successfully deleted', 'The existing attribute has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteAttributeFail$ = createEffect(() => 
+    /**
+     * Displays delete attribute error notification.
+     */
+    deleteAttributeFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(attributeActions.deleteAttributeFail),
             tap(() => this.toastr.error('Failure to delete attribute', 'The existing attribute could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private attributeService: AttributeService,
diff --git a/client/src/app/metamodel/effects/column.effects.spec.ts b/client/src/app/metamodel/effects/column.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..64ef0d2deab1e4510daffd389974ebf68d60a954
--- /dev/null
+++ b/client/src/app/metamodel/effects/column.effects.spec.ts
@@ -0,0 +1,88 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { ColumnEffects } from './column.effects';
+import { ColumnService } from '../services/column.service';
+import * as columnActions from '../actions/column.actions';
+import { COLUMN_LIST } from '../../../test-data';
+import * as datasetSelector from '../selectors/dataset.selector';
+
+describe('[Metamodel][Effects] ColumnEffects', () => {
+    let actions = new Observable();
+    let effects: ColumnEffects;
+    let metadata: EffectsMetadata<ColumnEffects>;
+    let service: ColumnService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                ColumnEffects,
+                { provide: ColumnService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(ColumnEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(ColumnService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadColumns$ effect', () => {
+        it('should dispatch the loadColumnListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = columnActions.loadColumnList();
+            const outcome = columnActions.loadColumnListSuccess({ columns: COLUMN_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: COLUMN_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveColumns = jest.fn(() => response);
+
+            expect(effects.loadColumns$).toBeObservable(expected);
+            expect(service.retrieveColumns).toHaveBeenCalledWith('myDataset');
+        });
+
+        it('should dispatch the loadColumnListFail action on HTTP failure', () => {
+            const action = columnActions.loadColumnList();
+            const error = new Error();
+            const outcome = columnActions.loadColumnListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveColumns = jest.fn(() => response);
+
+            expect(effects.loadColumns$).toBeObservable(expected);
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/column.effects.ts b/client/src/app/metamodel/effects/column.effects.ts
index 53399092ae075c8302d67a01ed281cd5d35b6fd3..0e11b28f27add041602baf97520a2e0c021eb6fc 100644
--- a/client/src/app/metamodel/effects/column.effects.ts
+++ b/client/src/app/metamodel/effects/column.effects.ts
@@ -17,10 +17,18 @@ import { map, mergeMap, catchError } from 'rxjs/operators';
 import * as columnActions from '../actions/column.actions';
 import { ColumnService } from '../services/column.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+/**
+ * @class
+ * @classdesc Column effects.
+ */
 @Injectable()
 export class ColumnEffects {
-    loadColumns$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve survey list.
+     */
+    loadColumns$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(columnActions.loadColumnList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -32,7 +40,7 @@ export class ColumnEffects {
             )
         )
     );
- 
+
     constructor(
         private actions$: Actions,
         private columnService: ColumnService,
diff --git a/client/src/app/metamodel/effects/criteria-family.effects.spec.ts b/client/src/app/metamodel/effects/criteria-family.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..10ac482fdfe156d74aab60854e244b8b47628691
--- /dev/null
+++ b/client/src/app/metamodel/effects/criteria-family.effects.spec.ts
@@ -0,0 +1,312 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { ToastrService } from 'ngx-toastr';
+import { CriteriaFamilyEffects } from './criteria-family.effects';
+import { CriteriaFamilyService } from '../services/criteria-family.service';
+import * as criteriaFamilyActions from '../actions/criteria-family.actions';
+import { CRITERIA_FAMILY, CRITERIA_FAMILY_LIST } from '../../../test-data';
+import * as datasetSelector from '../selectors/dataset.selector';
+
+describe('[Metamodel][Effects] CriteriaFamilyEffects', () => {
+    let actions = new Observable();
+    let effects: CriteriaFamilyEffects;
+    let metadata: EffectsMetadata<CriteriaFamilyEffects>;
+    let service: CriteriaFamilyService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                CriteriaFamilyEffects,
+                { provide: CriteriaFamilyService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(CriteriaFamilyEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(CriteriaFamilyService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadCriteriaFamilies$ effect', () => {
+        it('should dispatch the loadCriteriaFamilyListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = criteriaFamilyActions.loadCriteriaFamilyList();
+            const outcome = criteriaFamilyActions.loadCriteriaFamilyListSuccess({ criteriaFamilies: CRITERIA_FAMILY_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CRITERIA_FAMILY_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveCriteriaFamilyList = jest.fn(() => response);
+
+            expect(effects.loadCriteriaFamilies$).toBeObservable(expected);
+            expect(service.retrieveCriteriaFamilyList).toHaveBeenCalledWith('myDataset');
+        });
+
+        it('should dispatch the loadCriteriaFamilyListFail action on HTTP failure', () => {
+            const action = criteriaFamilyActions.loadCriteriaFamilyList();
+            const error = new Error();
+            const outcome = criteriaFamilyActions.loadCriteriaFamilyListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveCriteriaFamilyList = jest.fn(() => response);
+
+            expect(effects.loadCriteriaFamilies$).toBeObservable(expected);
+        });
+    });
+
+    describe('addCriteriaFamilies$ effect', () => {
+        it('should dispatch the addCriteriaFamilySuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = criteriaFamilyActions.addCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const outcome = criteriaFamilyActions.addCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CRITERIA_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.addCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.addCriteriaFamily$).toBeObservable(expected);
+            expect(service.addCriteriaFamily).toHaveBeenCalledWith('myDataset', CRITERIA_FAMILY);
+        });
+
+        it('should dispatch the addCriteriaFamilyFail action on HTTP failure', () => {
+            const action = criteriaFamilyActions.addCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const error = new Error();
+            const outcome = criteriaFamilyActions.addCriteriaFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.addCriteriaFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('addCriteriaFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addCriteriaFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = criteriaFamilyActions.addCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addCriteriaFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Criteria family successfully added',
+                'The new criteria family was added into the database'
+            );
+        });
+    });
+
+    describe('addCriteriaFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addCriteriaFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = criteriaFamilyActions.addCriteriaFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addCriteriaFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add criteria family',
+                'The new criteria family could not be added into the database'
+            );
+        });
+    });
+
+    describe('editCriteriaFamily$ effect', () => {
+        it('should dispatch the editCriteriaFamilySuccess action on success', () => {
+            const action = criteriaFamilyActions.editCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const outcome = criteriaFamilyActions.editCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CRITERIA_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.editCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.editCriteriaFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editCriteriaFamilyFail action on HTTP failure', () => {
+            const action = criteriaFamilyActions.editCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const error = new Error();
+            const outcome = criteriaFamilyActions.editCriteriaFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.editCriteriaFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('editCriteriaFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editCriteriaFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = criteriaFamilyActions.editCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editCriteriaFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Criteria family successfully edited',
+                'The existing criteria family has been edited into the database'
+            );
+        });
+    });
+
+    describe('editCriteriaFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editCriteriaFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = criteriaFamilyActions.editCriteriaFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editCriteriaFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit criteria family',
+                'The existing criteria family could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteCriteriaFamily$ effect', () => {
+        it('should dispatch the deleteSurveySuccess action on success', () => {
+            const action = criteriaFamilyActions.deleteCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const outcome = criteriaFamilyActions.deleteCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CRITERIA_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.deleteCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.deleteCriteriaFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteCriteriaFamilyFail action on HTTP failure', () => {
+            const action = criteriaFamilyActions.deleteCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY });
+            const error = new Error();
+            const outcome = criteriaFamilyActions.deleteCriteriaFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteCriteriaFamily = jest.fn(() => response);
+
+            expect(effects.deleteCriteriaFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteCriteriaFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteCriteriaFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = criteriaFamilyActions.deleteCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteCriteriaFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Criteria family successfully deleted',
+                'The existing criteria family has been deleted'
+            );
+        });
+    });
+
+    describe('deleteCriteriaFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteCriteriaFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = criteriaFamilyActions.deleteCriteriaFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteCriteriaFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete criteria family',
+                'The existing criteria family could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/criteria-family.effects.ts b/client/src/app/metamodel/effects/criteria-family.effects.ts
index 95894f4cf924558bf1f2fb125c6eebece5d65237..e72d3178a3aa9d1ddac4032d69cfa1a741d96035 100644
--- a/client/src/app/metamodel/effects/criteria-family.effects.ts
+++ b/client/src/app/metamodel/effects/criteria-family.effects.ts
@@ -18,10 +18,18 @@ import { ToastrService } from 'ngx-toastr';
 import * as criteriaFamilyActions from '../actions/criteria-family.actions';
 import { CriteriaFamilyService } from '../services/criteria-family.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+/**
+ * @class
+ * @classdesc Criteria family effects.
+ */
 @Injectable()
 export class CriteriaFamilyEffects {
-    loadCriteriaFamilys$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve criteria family list.
+     */
+    loadCriteriaFamilies$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.loadCriteriaFamilyList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -34,7 +42,10 @@ export class CriteriaFamilyEffects {
         )
     );
 
-    addCriteriaFamily$ = createEffect(() => 
+    /**
+     * Calls action to add a criteria family.
+     */
+    addCriteriaFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.addCriteriaFamily),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -47,21 +58,30 @@ export class CriteriaFamilyEffects {
         )
     );
 
-    addCriteriaFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays add criteria family success notification.
+     */
+    addCriteriaFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.addCriteriaFamilySuccess),
             tap(() => this.toastr.success('Criteria family successfully added', 'The new criteria family was added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addCriteriaFamilyFail$ = createEffect(() => 
+    /**
+     * Displays add criteria family error notification.
+     */
+    addCriteriaFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.addCriteriaFamilyFail),
             tap(() => this.toastr.error('Failure to add criteria family', 'The new criteria family could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editCriteriaFamily$ = createEffect(() => 
+    /**
+     * Calls action to modify a criteria family.
+     */
+    editCriteriaFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.editCriteriaFamily),
             mergeMap(action => this.criteriaFamilyService.editCriteriaFamily(action.criteriaFamily)
@@ -73,21 +93,30 @@ export class CriteriaFamilyEffects {
         )
     );
 
-    editCriteriaFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays edit criteria family success notification.
+     */
+    editCriteriaFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.editCriteriaFamilySuccess),
             tap(() => this.toastr.success('Criteria family successfully edited', 'The existing criteria family has been edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editCriteriaFamilyFail$ = createEffect(() => 
+    /**
+     * Displays edit criteria family error notification.
+     */
+    editCriteriaFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.editCriteriaFamilyFail),
             tap(() => this.toastr.error('Failure to edit criteria family', 'The existing criteria family could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteCriteriaFamily$ = createEffect(() => 
+    /**
+     * Calls action to remove a criteria family.
+     */
+    deleteCriteriaFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.deleteCriteriaFamily),
             mergeMap(action => this.criteriaFamilyService.deleteCriteriaFamily(action.criteriaFamily.id)
@@ -99,20 +128,26 @@ export class CriteriaFamilyEffects {
         )
     );
 
-    deleteCriteriaFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays delete criteria family success notification.
+     */
+    deleteCriteriaFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.deleteCriteriaFamilySuccess),
             tap(() => this.toastr.success('Criteria family successfully deleted', 'The existing criteria family has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteCriteriaFamilyFail$ = createEffect(() => 
+    /**
+     * Displays delete criteria family error notification.
+     */
+    deleteCriteriaFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(criteriaFamilyActions.deleteCriteriaFamilyFail),
             tap(() => this.toastr.error('Failure to delete criteria family', 'The existing criteria family could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private criteriaFamilyService: CriteriaFamilyService,
diff --git a/client/src/app/metamodel/effects/database.effects.spec.ts b/client/src/app/metamodel/effects/database.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65326e24aa2d975960b63f61662f57a8254f0c90
--- /dev/null
+++ b/client/src/app/metamodel/effects/database.effects.spec.ts
@@ -0,0 +1,298 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { DatabaseEffects } from './database.effects';
+import { DatabaseService } from '../services/database.service';
+import * as databaseActions from '../actions/database.actions';
+import { DATABASE, DATABASE_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] DatabaseEffects', () => {
+    let actions = new Observable();
+    let effects: DatabaseEffects;
+    let metadata: EffectsMetadata<DatabaseEffects>;
+    let service: DatabaseService;
+    let toastr: ToastrService;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                DatabaseEffects,
+                { provide: DatabaseService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(DatabaseEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(DatabaseService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadDatabases$ effect', () => {
+        it('should dispatch the loadDatabaseListSuccess action on success', () => {
+            const action = databaseActions.loadDatabaseList();
+            const outcome = databaseActions.loadDatabaseListSuccess({ databases: DATABASE_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATABASE_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatabaseList = jest.fn(() => response);
+
+            expect(effects.loadDatabases$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadDatabaseListFail action on HTTP failure', () => {
+            const action = databaseActions.loadDatabaseList();
+            const error = new Error();
+            const outcome = databaseActions.loadDatabaseListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatabaseList = jest.fn(() => response);
+
+            expect(effects.loadDatabases$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDatabase$ effect', () => {
+        it('should dispatch the addDatabaseSuccess action on success', () => {
+            const action = databaseActions.addDatabase({ database: DATABASE });
+            const outcome = databaseActions.addDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATABASE });
+            const expected = cold('--b', { b: outcome });
+            service.addDatabase = jest.fn(() => response);
+
+            expect(effects.addDatabase$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addDatabaseFail action on HTTP failure', () => {
+            const action = databaseActions.addDatabase({ database: DATABASE });
+            const error = new Error();
+            const outcome = databaseActions.addDatabaseFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addDatabase = jest.fn(() => response);
+
+            expect(effects.addDatabase$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDatabaseSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatabaseSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = databaseActions.addDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addDatabaseSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Database successfully added',
+                'The new database was added into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/database/database-list']);
+        });
+    });
+
+    describe('addDatabaseFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatabaseFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = databaseActions.addDatabaseFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addDatabaseFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add database',
+                'The new database could not be added into the database'
+            );
+        });
+    });
+
+    describe('editDatabase$ effect', () => {
+        it('should dispatch the editDatabaseSuccess action on success', () => {
+            const action = databaseActions.editDatabase({ database: DATABASE });
+            const outcome = databaseActions.editDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATABASE });
+            const expected = cold('--b', { b: outcome });
+            service.editDatabase = jest.fn(() => response);
+
+            expect(effects.editDatabase$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editDatabaseFail action on HTTP failure', () => {
+            const action = databaseActions.editDatabase({ database: DATABASE });
+            const error = new Error();
+            const outcome = databaseActions.editDatabaseFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editDatabase = jest.fn(() => response);
+
+            expect(effects.editDatabase$).toBeObservable(expected);
+        });
+    });
+
+    describe('editDatabaseSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatabaseSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = databaseActions.editDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editDatabaseSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Database successfully edited',
+                'The existing database has been edited into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/database/database-list']);
+        });
+    });
+
+    describe('editDatabaseFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatabaseFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = databaseActions.editDatabaseFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editDatabaseFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit database',
+                'The existing database could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteDatabase$ effect', () => {
+        it('should dispatch the deleteDatabaseSuccess action on success', () => {
+            const action = databaseActions.deleteDatabase({ database: DATABASE });
+            const outcome = databaseActions.deleteDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATABASE });
+            const expected = cold('--b', { b: outcome });
+            service.deleteDatabase = jest.fn(() => response);
+
+            expect(effects.deleteDatabase$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteDatabaseFail action on HTTP failure', () => {
+            const action = databaseActions.deleteDatabase({ database: DATABASE });
+            const error = new Error();
+            const outcome = databaseActions.deleteDatabaseFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteDatabase = jest.fn(() => response);
+
+            expect(effects.deleteDatabase$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteDatabaseSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatabaseSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = databaseActions.deleteDatabaseSuccess({ database: DATABASE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatabaseSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Database successfully deleted',
+                'The existing database has been deleted'
+            );
+        });
+    });
+
+    describe('deleteDatabaseFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatabaseFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = databaseActions.deleteDatabaseFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatabaseFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete database',
+                'The existing database could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/database.effects.ts b/client/src/app/metamodel/effects/database.effects.ts
index 0d2b2add88347833596b69248b8edf08bd5737e7..7be7fa4755703a9518afc3007ff579f64c930aec 100644
--- a/client/src/app/metamodel/effects/database.effects.ts
+++ b/client/src/app/metamodel/effects/database.effects.ts
@@ -17,10 +17,18 @@ import { ToastrService } from 'ngx-toastr';
 
 import * as databaseActions from '../actions/database.actions';
 import { DatabaseService } from '../services/database.service';
- 
+
+/**
+ * @class
+ * @classdesc Database effects.
+ */
 @Injectable()
 export class DatabaseEffects {
-    loadDatabases$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve database list.
+     */
+    loadDatabases$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(databaseActions.loadDatabaseList),
             mergeMap(() => this.databaseService.retrieveDatabaseList()
@@ -32,7 +40,10 @@ export class DatabaseEffects {
         )
     );
 
-    addDatabase$ = createEffect(() => 
+    /**
+     * Calls action to add a database.
+     */
+    addDatabase$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(databaseActions.addDatabase),
             mergeMap(action => this.databaseService.addDatabase(action.database)
@@ -44,24 +55,33 @@ export class DatabaseEffects {
         )
     );
 
-    addDatabaseSuccess$ = createEffect(() => 
+    /**
+     * Displays add database success notification.
+     */
+    addDatabaseSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.addDatabaseSuccess),
             tap(() => {
                 this.router.navigate(['/admin/database/database-list']);
                 this.toastr.success('Database successfully added', 'The new database was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addDatabaseFail$ = createEffect(() => 
+    /**
+     * Displays add database error notification.
+     */
+    addDatabaseFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.addDatabaseFail),
             tap(() => this.toastr.error('Failure to add database', 'The new database could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDatabase$ = createEffect(() => 
+    /**
+     * Calls action to modify a database.
+     */
+    editDatabase$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(databaseActions.editDatabase),
             mergeMap(action => this.databaseService.editDatabase(action.database)
@@ -73,24 +93,33 @@ export class DatabaseEffects {
         )
     );
 
-    editDatabaseSuccess$ = createEffect(() => 
+    /**
+     * Displays edit database success notification.
+     */
+    editDatabaseSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.editDatabaseSuccess),
             tap(() => {
                 this.router.navigate(['/admin/database/database-list']);
                 this.toastr.success('Database successfully edited', 'The existing database has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDatabaseFail$ = createEffect(() => 
+    /**
+     * Displays edit database error notification.
+     */
+    editDatabaseFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.editDatabaseFail),
             tap(() => this.toastr.error('Failure to edit database', 'The existing database could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDatabase$ = createEffect(() => 
+    /**
+     * Calls action to remove a database.
+     */
+    deleteDatabase$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(databaseActions.deleteDatabase),
             mergeMap(action => this.databaseService.deleteDatabase(action.database.id)
@@ -102,20 +131,26 @@ export class DatabaseEffects {
         )
     );
 
-    deleteDatabaseSuccess$ = createEffect(() => 
+    /**
+     * Displays delete database success notification.
+     */
+    deleteDatabaseSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.deleteDatabaseSuccess),
             tap(() => this.toastr.success('Database successfully deleted', 'The existing database has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDatabaseFail$ = createEffect(() => 
+    /**
+     * Displays delete database error notification.
+     */
+    deleteDatabaseFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(databaseActions.deleteDatabaseFail),
             tap(() => this.toastr.error('Failure to delete database', 'The existing database could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private databaseService: DatabaseService,
diff --git a/client/src/app/metamodel/effects/dataset-family.effects.spec.ts b/client/src/app/metamodel/effects/dataset-family.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f44343882971051ccca6d06e7512827c954feb60
--- /dev/null
+++ b/client/src/app/metamodel/effects/dataset-family.effects.spec.ts
@@ -0,0 +1,277 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { DatasetFamilyEffects } from './dataset-family.effects';
+import { DatasetFamilyService } from '../services/dataset-family.service';
+import * as datasetFamilyActions from '../actions/dataset-family.actions';
+import * as instanceSelector from '../selectors/instance.selector';
+import { DATASET_FAMILY_LIST, DATASET_FAMILY } from '../../../test-data';
+
+describe('[Metamodel][Effects] DatasetFamilyEffects', () => {
+    let actions = new Observable();
+    let effects: DatasetFamilyEffects;
+    let metadata: EffectsMetadata<DatasetFamilyEffects>;
+    let service: DatasetFamilyService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockInstanceSelectorSelectInstanceNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                DatasetFamilyEffects,
+                { provide: DatasetFamilyService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(DatasetFamilyEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(DatasetFamilyService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+            instanceSelector.selectInstanceNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadDatasetFamilies$ effect', () => {
+        it('should dispatch the loadDatasetFamilyListSuccess action on success', () => {
+            mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+                instanceSelector.selectInstanceNameByRoute,'myInstance'
+            );
+
+            const action = datasetFamilyActions.loadDatasetFamilyList();
+            const outcome = datasetFamilyActions.loadDatasetFamilyListSuccess({ datasetFamilies: DATASET_FAMILY_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET_FAMILY_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatasetFamilyList = jest.fn(() => response);
+
+            expect(effects.loadDatasetFamilies$).toBeObservable(expected);
+            expect(service.retrieveDatasetFamilyList).toHaveBeenCalledWith('myInstance');
+        });
+
+        it('should dispatch the loadDatasetFamilyListFail action on HTTP failure', () => {
+            const action = datasetFamilyActions.loadDatasetFamilyList();
+            const error = new Error();
+            const outcome = datasetFamilyActions.loadDatasetFamilyListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatasetFamilyList = jest.fn(() => response);
+
+            expect(effects.loadDatasetFamilies$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDatasetFamily$ effect', () => {
+        it('should dispatch the addDatasetFamilySuccess action on success', () => {
+            const action = datasetFamilyActions.addDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const outcome = datasetFamilyActions.addDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.addDatasetFamily = jest.fn(() => response);
+
+            expect(effects.addDatasetFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addDatasetFamilyFail action on HTTP failure', () => {
+            const action = datasetFamilyActions.addDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const error = new Error();
+            const outcome = datasetFamilyActions.addDatasetFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addDatasetFamily = jest.fn(() => response);
+
+            expect(effects.addDatasetFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDatasetFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatasetFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('addDatasetFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatasetFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetFamilyActions.addDatasetFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addDatasetFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add dataset family',
+                'The new dataset family could not be added into the database'
+            );
+        });
+    });
+
+    describe('editDatasetFamily$ effect', () => {
+        it('should dispatch the editDatasetFamilySuccess action on success', () => {
+            const action = datasetFamilyActions.editDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const outcome = datasetFamilyActions.editDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.editDatasetFamily = jest.fn(() => response);
+
+            expect(effects.editDatasetFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editDatasetFamilyFail action on HTTP failure', () => {
+            const action = datasetFamilyActions.editDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const error = new Error();
+            const outcome = datasetFamilyActions.editDatasetFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editDatasetFamily = jest.fn(() => response);
+
+            expect(effects.editDatasetFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('editDatasetFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatasetFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('editDatasetFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatasetFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetFamilyActions.editDatasetFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editDatasetFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit dataset family',
+                'The existing dataset family could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteDatasetFamily$ effect', () => {
+        it('should dispatch the deleteDatasetFamilySuccess action on success', () => {
+            const action = datasetFamilyActions.deleteDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const outcome = datasetFamilyActions.deleteDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.deleteDatasetFamily = jest.fn(() => response);
+
+            expect(effects.deleteDatasetFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteDatasetFamilyFail action on HTTP failure', () => {
+            const action = datasetFamilyActions.deleteDatasetFamily({ datasetFamily: DATASET_FAMILY });
+            const error = new Error();
+            const outcome = datasetFamilyActions.deleteDatasetFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteDatasetFamily = jest.fn(() => response);
+
+            expect(effects.deleteDatasetFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteDatasetFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatasetFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = datasetFamilyActions.deleteDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatasetFamilySuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Dataset family successfully deleted',
+                'The existing dataset family has been deleted'
+            );
+        });
+    });
+
+    describe('deleteDatasetFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatasetFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetFamilyActions.deleteDatasetFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatasetFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete dataset family',
+                'The existing dataset family could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/dataset-family.effects.ts b/client/src/app/metamodel/effects/dataset-family.effects.ts
index c3aa0227097b011558ff28218103a39ccf42e5c4..c2a3925a8b491b96191dc568a3bf08093c49a27a 100644
--- a/client/src/app/metamodel/effects/dataset-family.effects.ts
+++ b/client/src/app/metamodel/effects/dataset-family.effects.ts
@@ -19,10 +19,19 @@ import { ToastrService } from 'ngx-toastr';
 import * as datasetFamilyActions from '../actions/dataset-family.actions';
 import { DatasetFamilyService } from '../services/dataset-family.service';
 import * as instanceSelector from '../selectors/instance.selector';
- 
+
+
+/**
+ * @class
+ * @classdesc Dataset family effects.
+ */
 @Injectable()
 export class DatasetFamilyEffects {
-    loadDatasetFamilys$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve dataset family list.
+     */
+    loadDatasetFamilies$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.loadDatasetFamilyList),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -35,7 +44,10 @@ export class DatasetFamilyEffects {
         )
     );
 
-    addDatasetFamily$ = createEffect(() => 
+    /**
+     * Calls action to add a dataset family.
+     */
+    addDatasetFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.addDatasetFamily),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -48,7 +60,10 @@ export class DatasetFamilyEffects {
         )
     );
 
-    addDatasetFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays add dataset family success notification.
+     */
+    addDatasetFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.addDatasetFamilySuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -56,17 +71,23 @@ export class DatasetFamilyEffects {
                 this.router.navigate([`/admin/configure-instance/${instanceName}`]);
                 this.toastr.success('Dataset family successfully added', 'The new dataset family was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addDatasetFamilyFail$ = createEffect(() => 
+    /**
+     * Displays add dataset family fail notification.
+     */
+    addDatasetFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.addDatasetFamilyFail),
             tap(() => this.toastr.error('Failure to add dataset family', 'The new dataset family could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDatasetFamily$ = createEffect(() => 
+    /**
+     * Calls action to modify a dataset family.
+     */
+    editDatasetFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.editDatasetFamily),
             mergeMap(action => this.datasetFamilyService.editDatasetFamily(action.datasetFamily)
@@ -78,7 +99,10 @@ export class DatasetFamilyEffects {
         )
     );
 
-    editDatasetFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays edit dataset family success notification.
+     */
+    editDatasetFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.editDatasetFamilySuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -86,17 +110,23 @@ export class DatasetFamilyEffects {
                 this.router.navigate([`/admin/configure-instance/${instanceName}`]);
                 this.toastr.success('Dataset family successfully edited', 'The existing dataset family has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDatasetFamilyFail$ = createEffect(() => 
+    /**
+     * Displays edit dataset family error notification.
+     */
+    editDatasetFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.editDatasetFamilyFail),
             tap(() => this.toastr.error('Failure to edit dataset family', 'The existing dataset family could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDatasetFamily$ = createEffect(() => 
+    /**
+     * Calls action to remove a dataset family.
+     */
+    deleteDatasetFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.deleteDatasetFamily),
             mergeMap(action => this.datasetFamilyService.deleteDatasetFamily(action.datasetFamily.id)
@@ -108,20 +138,26 @@ export class DatasetFamilyEffects {
         )
     );
 
-    deleteDatasetFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays delete dataset family success notification.
+     */
+    deleteDatasetFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.deleteDatasetFamilySuccess),
             tap(() => this.toastr.success('Dataset family successfully deleted', 'The existing dataset family has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDatasetFamilyFail$ = createEffect(() => 
+    /**
+     * Displays delete dataset family error notification.
+     */
+    deleteDatasetFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetFamilyActions.deleteDatasetFamilyFail),
             tap(() => this.toastr.error('Failure to delete dataset family', 'The existing dataset family could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private datasetFamilyService: DatasetFamilyService,
diff --git a/client/src/app/metamodel/effects/dataset.effects.spec.ts b/client/src/app/metamodel/effects/dataset.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..597a0aaedab3d382c1571dbfc2a4ee72a6b322cc
--- /dev/null
+++ b/client/src/app/metamodel/effects/dataset.effects.spec.ts
@@ -0,0 +1,277 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { DatasetEffects } from './dataset.effects';
+import { DatasetService } from '../services/dataset.service';
+import * as datasetActions from '../actions/dataset.actions';
+import * as instanceSelector from '../selectors/instance.selector';
+import { DATASET, DATASET_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] DatasetEffects', () => {
+    let actions = new Observable();
+    let effects: DatasetEffects;
+    let metadata: EffectsMetadata<DatasetEffects>;
+    let service: DatasetService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockInstanceSelectorSelectInstanceNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                DatasetEffects,
+                { provide: DatasetService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(DatasetEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(DatasetService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+            instanceSelector.selectInstanceNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadDatasets$ effect', () => {
+        it('should dispatch the loadDatasetListSuccess action on success', () => {
+            mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+                instanceSelector.selectInstanceNameByRoute,'myInstance'
+            );
+
+            const action = datasetActions.loadDatasetList();
+            const outcome = datasetActions.loadDatasetListSuccess({ datasets: DATASET_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatasetList = jest.fn(() => response);
+
+            expect(effects.loadDatasets$).toBeObservable(expected);
+            expect(service.retrieveDatasetList).toHaveBeenCalledWith('myInstance');
+        });
+
+        it('should dispatch the loadDatasetListFail action on HTTP failure', () => {
+            const action = datasetActions.loadDatasetList();
+            const error = new Error();
+            const outcome = datasetActions.loadDatasetListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveDatasetList = jest.fn(() => response);
+
+            expect(effects.loadDatasets$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDataset$ effect', () => {
+        it('should dispatch the addDatasetSuccess action on success', () => {
+            const action = datasetActions.addDataset({ dataset: DATASET });
+            const outcome = datasetActions.addDatasetSuccess({ dataset: DATASET });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET });
+            const expected = cold('--b', { b: outcome });
+            service.addDataset = jest.fn(() => response);
+
+            expect(effects.addDataset$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addDatasetFail action on HTTP failure', () => {
+            const action = datasetActions.addDataset({ dataset: DATASET });
+            const error = new Error();
+            const outcome = datasetActions.addDatasetFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addDataset = jest.fn(() => response);
+
+            expect(effects.addDataset$).toBeObservable(expected);
+        });
+    });
+
+    describe('addDatasetSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatasetSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('addDatasetFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addDatasetFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetActions.addDatasetFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addDatasetFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add dataset',
+                'The new dataset could not be added into the database'
+            );
+        });
+    });
+
+    describe('editDataset$ effect', () => {
+        it('should dispatch the editDatasetSuccess action on success', () => {
+            const action = datasetActions.editDataset({ dataset: DATASET });
+            const outcome = datasetActions.editDatasetSuccess({ dataset: DATASET });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET });
+            const expected = cold('--b', { b: outcome });
+            service.editDataset = jest.fn(() => response);
+
+            expect(effects.editDataset$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editDatasetFail action on HTTP failure', () => {
+            const action = datasetActions.editDataset({ dataset: DATASET });
+            const error = new Error();
+            const outcome = datasetActions.editDatasetFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editDataset = jest.fn(() => response);
+
+            expect(effects.editDataset$).toBeObservable(expected);
+        });
+    });
+
+    describe('editDatasetSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatasetSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('editDatasetFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editDatasetFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetActions.editDatasetFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editDatasetFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit dataset',
+                'The existing dataset could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteDataset$ effect', () => {
+        it('should dispatch the deleteDatasetSuccess action on success', () => {
+            const action = datasetActions.deleteDataset({ dataset: DATASET });
+            const outcome = datasetActions.deleteDatasetSuccess({ dataset: DATASET });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: DATASET });
+            const expected = cold('--b', { b: outcome });
+            service.deleteDataset = jest.fn(() => response);
+
+            expect(effects.deleteDataset$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteDatasetFail action on HTTP failure', () => {
+            const action = datasetActions.deleteDataset({ dataset: DATASET });
+            const error = new Error();
+            const outcome = datasetActions.deleteDatasetFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteDataset = jest.fn(() => response);
+
+            expect(effects.deleteDataset$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteDatasetSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatasetSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = datasetActions.deleteDatasetSuccess({ dataset: DATASET });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatasetSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Dataset successfully deleted',
+                'The existing dataset has been deleted'
+            );
+        });
+    });
+
+    describe('deleteDatasetFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteDatasetFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = datasetActions.deleteDatasetFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteDatasetFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete dataset',
+                'The existing dataset could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/dataset.effects.ts b/client/src/app/metamodel/effects/dataset.effects.ts
index cf0a0e3fee30c9f2b1179ee4993af68ed3e350c0..28894b6ffbe6c4b407f4ce2da36bc49897755b0c 100644
--- a/client/src/app/metamodel/effects/dataset.effects.ts
+++ b/client/src/app/metamodel/effects/dataset.effects.ts
@@ -19,10 +19,18 @@ import { ToastrService } from 'ngx-toastr';
 import * as datasetActions from '../actions/dataset.actions';
 import { DatasetService } from '../services/dataset.service';
 import * as instanceSelector from '../selectors/instance.selector';
- 
+
+/**
+ * @class
+ * @classdesc Dataset effects.
+ */
 @Injectable()
 export class DatasetEffects {
-    loadDatasets$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve dataset list.
+     */
+    loadDatasets$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetActions.loadDatasetList),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -35,7 +43,10 @@ export class DatasetEffects {
         )
     );
 
-    addDataset$ = createEffect(() => 
+    /**
+     * Calls action to add a dataset.
+     */
+    addDataset$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetActions.addDataset),
             mergeMap(action => this.datasetService.addDataset(action.dataset)
@@ -47,7 +58,10 @@ export class DatasetEffects {
         )
     );
 
-    addDatasetSuccess$ = createEffect(() => 
+    /**
+     * Displays add dataset success notification.
+     */
+    addDatasetSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.addDatasetSuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -55,17 +69,23 @@ export class DatasetEffects {
                 this.router.navigate([`/admin/instance/configure-instance/${instanceName}`]);
                 this.toastr.success('Dataset successfully added', 'The new dataset was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addDatasetFail$ = createEffect(() => 
+    /**
+     * Displays add dataset error notification.
+     */
+    addDatasetFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.addDatasetFail),
             tap(() => this.toastr.error('Failure to add dataset', 'The new dataset could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDataset$ = createEffect(() => 
+    /**
+     * Calls action to add a dataset.
+     */
+    editDataset$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetActions.editDataset),
             mergeMap(action => this.datasetService.editDataset(action.dataset)
@@ -77,7 +97,10 @@ export class DatasetEffects {
         )
     );
 
-    editDatasetSuccess$ = createEffect(() => 
+    /**
+     * Displays modify dataset success notification.
+     */
+    editDatasetSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.editDatasetSuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -85,17 +108,23 @@ export class DatasetEffects {
                 this.router.navigate([`/admin/instance/configure-instance/${instanceName}`]);
                 this.toastr.success('Dataset successfully edited', 'The existing dataset has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editDatasetFail$ = createEffect(() => 
+    /**
+     * Displays modify dataset error notification.
+     */
+    editDatasetFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.editDatasetFail),
             tap(() => this.toastr.error('Failure to edit dataset', 'The existing dataset could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDataset$ = createEffect(() => 
+    /**
+     * Calls action to add a dataset.
+     */
+    deleteDataset$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(datasetActions.deleteDataset),
             mergeMap(action => this.datasetService.deleteDataset(action.dataset.name)
@@ -107,20 +136,26 @@ export class DatasetEffects {
         )
     );
 
-    deleteDatasetSuccess$ = createEffect(() => 
+    /**
+     * Displays remove dataset success notification.
+     */
+    deleteDatasetSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.deleteDatasetSuccess),
             tap(() => this.toastr.success('Dataset successfully deleted', 'The existing dataset has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteDatasetFail$ = createEffect(() => 
+    /**
+     * Displays remove dataset error notification.
+     */
+    deleteDatasetFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(datasetActions.deleteDatasetFail),
             tap(() => this.toastr.error('Failure to delete dataset', 'The existing dataset could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private datasetService: DatasetService,
diff --git a/client/src/app/metamodel/effects/group.effects.spec.ts b/client/src/app/metamodel/effects/group.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56c9c8d56d97181fb3a9461a6aa14cd302bf5d07
--- /dev/null
+++ b/client/src/app/metamodel/effects/group.effects.spec.ts
@@ -0,0 +1,277 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable, of } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { GroupEffects } from './group.effects';
+import { GroupService } from '../services/group.service';
+import * as groupActions from '../actions/group.actions';
+import * as instanceSelector from '../selectors/instance.selector';
+import { GROUP, GROUP_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] GroupEffects', () => {
+    let actions = new Observable();
+    let effects: GroupEffects;
+    let metadata: EffectsMetadata<GroupEffects>;
+    let service: GroupService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockInstanceSelectorSelectInstanceNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                GroupEffects,
+                { provide: GroupService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(GroupEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(GroupService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+            instanceSelector.selectInstanceNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadGroups$ effect', () => {
+        it('should dispatch the loadGroupListSuccess action on success', () => {
+            mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector(
+                instanceSelector.selectInstanceNameByRoute,'myInstance'
+            );
+
+            const action = groupActions.loadGroupList();
+            const outcome = groupActions.loadGroupListSuccess({ groups: GROUP_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: GROUP_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveGroupList = jest.fn(() => response);
+
+            expect(effects.loadGroups$).toBeObservable(expected);
+            expect(service.retrieveGroupList).toHaveBeenCalledWith('myInstance');
+        });
+
+        it('should dispatch the loadGroupListFail action on HTTP failure', () => {
+            const action = groupActions.loadGroupList();
+            const error = new Error();
+            const outcome = groupActions.loadGroupListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveGroupList = jest.fn(() => response);
+
+            expect(effects.loadGroups$).toBeObservable(expected);
+        });
+    });
+
+    describe('addGroup$ effect', () => {
+        it('should dispatch the addGroupSuccess action on success', () => {
+            const action = groupActions.addGroup({ group: GROUP });
+            const outcome = groupActions.addGroupSuccess({ group: GROUP });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: GROUP });
+            const expected = cold('--b', { b: outcome });
+            service.addGroup = jest.fn(() => response);
+
+            expect(effects.addGroup$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addGroupFail action on HTTP failure', () => {
+            const action = groupActions.addGroup({ group: GROUP });
+            const error = new Error();
+            const outcome = groupActions.addGroupFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addGroup = jest.fn(() => response);
+
+            expect(effects.addGroup$).toBeObservable(expected);
+        });
+    });
+
+    describe('addGroupSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addGroupSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('addGroupFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addGroupFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = groupActions.addGroupFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addGroupFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add group',
+                'The new group could not be added into the database'
+            );
+        });
+    });
+
+    describe('editGroup$ effect', () => {
+        it('should dispatch the editGroupSuccess action on success', () => {
+            const action = groupActions.editGroup({ group: GROUP });
+            const outcome = groupActions.editGroupSuccess({ group: GROUP });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: GROUP });
+            const expected = cold('--b', { b: outcome });
+            service.editGroup = jest.fn(() => response);
+
+            expect(effects.editGroup$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editGroupFail action on HTTP failure', () => {
+            const action = groupActions.editGroup({ group: GROUP });
+            const error = new Error();
+            const outcome = groupActions.editGroupFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editGroup = jest.fn(() => response);
+
+            expect(effects.editGroup$).toBeObservable(expected);
+        });
+    });
+
+    describe('editGroupSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editGroupSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+    });
+
+    describe('editGroupFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editGroupFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = groupActions.editGroupFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editGroupFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit group',
+                'The existing group could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteGroup$ effect', () => {
+        it('should dispatch the deleteGroupSuccess action on success', () => {
+            const action = groupActions.deleteGroup({ group: GROUP });
+            const outcome = groupActions.deleteGroupSuccess({ group: GROUP });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: GROUP });
+            const expected = cold('--b', { b: outcome });
+            service.deleteGroup = jest.fn(() => response);
+
+            expect(effects.deleteGroup$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteGroupFail action on HTTP failure', () => {
+            const action = groupActions.deleteGroup({ group: GROUP });
+            const error = new Error();
+            const outcome = groupActions.deleteGroupFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteGroup = jest.fn(() => response);
+
+            expect(effects.deleteGroup$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteGroupSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteGroupSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = groupActions.deleteGroupSuccess({ group: GROUP });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteGroupSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Group successfully deleted',
+                'The existing group has been deleted'
+            );
+        });
+    });
+
+    describe('deleteGroupFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteGroupFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = groupActions.deleteGroupFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteGroupFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete group',
+                'The existing group could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/group.effects.ts b/client/src/app/metamodel/effects/group.effects.ts
index 6f3567afd9e0a71550dfba4713df71b5cf69a80d..357186527289ceda7293d1f23f9d7dce80bc50b4 100644
--- a/client/src/app/metamodel/effects/group.effects.ts
+++ b/client/src/app/metamodel/effects/group.effects.ts
@@ -13,16 +13,24 @@ import { Router } from '@angular/router';
 import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects';
 import { Store } from '@ngrx/store';
 import { of } from 'rxjs';
-import { map, tap, mergeMap, catchError } from 'rxjs/operators';
+import { map, tap, mergeMap, catchError, finalize, take } from 'rxjs/operators';
 import { ToastrService } from 'ngx-toastr';
 
 import * as groupActions from '../actions/group.actions';
 import { GroupService } from '../services/group.service';
 import * as instanceSelector from '../selectors/instance.selector';
- 
+
+/**
+ * @class
+ * @classdesc Survey effects.
+ */
 @Injectable()
 export class GroupEffects {
-    loadGroups$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve group list.
+     */
+    loadGroups$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(groupActions.loadGroupList),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -35,7 +43,10 @@ export class GroupEffects {
         )
     );
 
-    addGroup$ = createEffect(() => 
+    /**
+     * Calls action to add a group.
+     */
+    addGroup$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(groupActions.addGroup),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -48,25 +59,35 @@ export class GroupEffects {
         )
     );
 
-    addGroupSuccess$ = createEffect(() => 
+    /**
+     * Displays add group success notification.
+     */
+    addGroupSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.addGroupSuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
-            tap(([, instanceName]) => {
-                this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/group`);
+            tap(instanceName => {
+                console.log(instanceName);
+                // this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/group`);
                 this.toastr.success('Group successfully added', 'The new group was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addGroupFail$ = createEffect(() => 
+    /**
+     * Displays add group error notification.
+     */
+    addGroupFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.addGroupFail),
             tap(() => this.toastr.error('Failure to add group', 'The new group could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editGroup$ = createEffect(() => 
+    /**
+     * Calls action to modify a group.
+     */
+    editGroup$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(groupActions.editGroup),
             mergeMap(action => this.groupService.editGroup(action.group)
@@ -78,7 +99,10 @@ export class GroupEffects {
         )
     );
 
-    editGroupSuccess$ = createEffect(() => 
+    /**
+     * Displays edit group success notification.
+     */
+    editGroupSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.editGroupSuccess),
             concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)),
@@ -86,17 +110,23 @@ export class GroupEffects {
                 this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/group`);
                 this.toastr.success('Group successfully edited', 'The existing group has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editGroupFail$ = createEffect(() => 
+    /**
+     * Displays edit group error notification.
+     */
+    editGroupFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.editGroupFail),
             tap(() => this.toastr.error('Failure to edit group', 'The existing group could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteGroup$ = createEffect(() => 
+    /**
+     * Calls action to remove a group.
+     */
+    deleteGroup$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(groupActions.deleteGroup),
             mergeMap(action => this.groupService.deleteGroup(action.group.id)
@@ -108,20 +138,26 @@ export class GroupEffects {
         )
     );
 
-    deleteGroupSuccess$ = createEffect(() => 
+    /**
+     * Displays delete group success notification.
+     */
+    deleteGroupSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.deleteGroupSuccess),
             tap(() => this.toastr.success('Group successfully deleted', 'The existing group has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteGroupFail$ = createEffect(() => 
+    /**
+     * Displays delete group error notification.
+     */
+    deleteGroupFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(groupActions.deleteGroupFail),
             tap(() => this.toastr.error('Failure to delete group', 'The existing group could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private groupService: GroupService,
diff --git a/client/src/app/metamodel/effects/instance.effects.spec.ts b/client/src/app/metamodel/effects/instance.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e30df89b7d24eb2225f7f6cdcbb317050e9587b
--- /dev/null
+++ b/client/src/app/metamodel/effects/instance.effects.spec.ts
@@ -0,0 +1,298 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { InstanceEffects } from './instance.effects';
+import { InstanceService } from '../services/instance.service';
+import * as instanceActions from '../actions/instance.actions';
+import { INSTANCE, INSTANCE_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] InstanceEffects', () => {
+    let actions = new Observable();
+    let effects: InstanceEffects;
+    let metadata: EffectsMetadata<InstanceEffects>;
+    let service: InstanceService;
+    let toastr: ToastrService;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                InstanceEffects,
+                { provide: InstanceService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(InstanceEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(InstanceService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadInstances$ effect', () => {
+        it('should dispatch the loadInstanceListSuccess action on success', () => {
+            const action = instanceActions.loadInstanceList();
+            const outcome = instanceActions.loadInstanceListSuccess({ instances: INSTANCE_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: INSTANCE_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveInstanceList = jest.fn(() => response);
+
+            expect(effects.loadInstances$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadSurveyListFail action on HTTP failure', () => {
+            const action = instanceActions.loadInstanceList();
+            const error = new Error();
+            const outcome = instanceActions.loadInstanceListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveInstanceList = jest.fn(() => response);
+
+            expect(effects.loadInstances$).toBeObservable(expected);
+        });
+    });
+
+    describe('addInstance$ effect', () => {
+        it('should dispatch the addSurveySuccess action on success', () => {
+            const action = instanceActions.addInstance({ instance: INSTANCE });
+            const outcome = instanceActions.addInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: INSTANCE });
+            const expected = cold('--b', { b: outcome });
+            service.addInstance = jest.fn(() => response);
+
+            expect(effects.addInstance$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addSurveyFail action on HTTP failure', () => {
+            const action = instanceActions.addInstance({ instance: INSTANCE });
+            const error = new Error();
+            const outcome = instanceActions.addInstanceFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addInstance = jest.fn(() => response);
+
+            expect(effects.addInstance$).toBeObservable(expected);
+        });
+    });
+
+    describe('addInstanceSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addInstanceSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = instanceActions.addInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addInstanceSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Instance successfully added',
+                'The new instance was added into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/instance/instance-list']);
+        });
+    });
+
+    describe('addInstanceFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addInstanceFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = instanceActions.addInstanceFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addInstanceFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add instance',
+                'The new instance could not be added into the database'
+            );
+        });
+    });
+
+    describe('editInstance$ effect', () => {
+        it('should dispatch the editInstanceSuccess action on success', () => {
+            const action = instanceActions.editInstance({ instance: INSTANCE });
+            const outcome = instanceActions.editInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: INSTANCE });
+            const expected = cold('--b', { b: outcome });
+            service.editInstance = jest.fn(() => response);
+
+            expect(effects.editInstance$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editSurveyFail action on HTTP failure', () => {
+            const action = instanceActions.editInstance({ instance: INSTANCE });
+            const error = new Error();
+            const outcome = instanceActions.editInstanceFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editInstance = jest.fn(() => response);
+
+            expect(effects.editInstance$).toBeObservable(expected);
+        });
+    });
+
+    describe('editInstanceSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editInstanceSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = instanceActions.editInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editInstanceSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Instance successfully edited',
+                'The existing instance has been edited into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/instance/instance-list']);
+        });
+    });
+
+    describe('editInstanceFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editInstanceFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = instanceActions.editInstanceFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editInstanceFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit instance',
+                'The existing instance could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteInstance$ effect', () => {
+        it('should dispatch the deleteInstanceSuccess action on success', () => {
+            const action = instanceActions.deleteInstance({ instance: INSTANCE });
+            const outcome = instanceActions.deleteInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: INSTANCE });
+            const expected = cold('--b', { b: outcome });
+            service.deleteInstance = jest.fn(() => response);
+
+            expect(effects.deleteInstance$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteSurveyFail action on HTTP failure', () => {
+            const action = instanceActions.deleteInstance({ instance: INSTANCE });
+            const error = new Error();
+            const outcome = instanceActions.deleteInstanceFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteInstance = jest.fn(() => response);
+
+            expect(effects.deleteInstance$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteInstanceSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteInstanceSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = instanceActions.deleteInstanceSuccess({ instance: INSTANCE });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteInstanceSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Instance successfully deleted',
+                'The existing instance has been deleted'
+            );
+        });
+    });
+
+    describe('deleteInstanceFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteInstanceFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = instanceActions.deleteInstanceFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteInstanceFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete instance',
+                'The existing instance could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/instance.effects.ts b/client/src/app/metamodel/effects/instance.effects.ts
index e83fb1e3c4f9a20154ea09706061d30893876905..bbd8dcc3945e8624d47c73c8a5226fb2a7b242cd 100644
--- a/client/src/app/metamodel/effects/instance.effects.ts
+++ b/client/src/app/metamodel/effects/instance.effects.ts
@@ -17,10 +17,18 @@ import { ToastrService } from 'ngx-toastr';
 
 import * as instanceActions from '../actions/instance.actions';
 import { InstanceService } from '../services/instance.service';
- 
+
+/**
+ * @class
+ * @classdesc Instance effects.
+ */
 @Injectable()
 export class InstanceEffects {
-    loadInstances$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve instance list.
+     */
+    loadInstances$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(instanceActions.loadInstanceList),
             mergeMap(() => this.instanceService.retrieveInstanceList()
@@ -32,7 +40,10 @@ export class InstanceEffects {
         )
     );
 
-    addInstance$ = createEffect(() => 
+    /**
+     * Calls action to add an instance.
+     */
+    addInstance$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(instanceActions.addInstance),
             mergeMap(action => this.instanceService.addInstance(action.instance)
@@ -44,24 +55,33 @@ export class InstanceEffects {
         )
     );
 
-    addInstanceSuccess$ = createEffect(() => 
+    /**
+     * Displays add instance success notification.
+     */
+    addInstanceSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.addInstanceSuccess),
             tap(() => {
                 this.router.navigate(['/admin/instance/instance-list']);
                 this.toastr.success('Instance successfully added', 'The new instance was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addInstanceFail$ = createEffect(() => 
+    /**
+     * Displays add instance fail notification.
+     */
+    addInstanceFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.addInstanceFail),
             tap(() => this.toastr.error('Failure to add instance', 'The new instance could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editInstance$ = createEffect(() => 
+    /**
+     * Calls action to modify an instance.
+     */
+    editInstance$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(instanceActions.editInstance),
             mergeMap(action => this.instanceService.editInstance(action.instance)
@@ -73,24 +93,33 @@ export class InstanceEffects {
         )
     );
 
-    editInstanceSuccess$ = createEffect(() => 
+    /**
+     * Displays edit instance success notification.
+     */
+    editInstanceSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.editInstanceSuccess),
             tap(() => {
                 this.router.navigate(['/admin/instance/instance-list']);
                 this.toastr.success('Instance successfully edited', 'The existing instance has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editInstanceFail$ = createEffect(() => 
+    /**
+     * Displays edit instance fail notification.
+     */
+    editInstanceFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.editInstanceFail),
             tap(() => this.toastr.error('Failure to edit instance', 'The existing instance could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteInstance$ = createEffect(() => 
+    /**
+     * Calls action to remove an instance.
+     */
+    deleteInstance$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(instanceActions.deleteInstance),
             mergeMap(action => this.instanceService.deleteInstance(action.instance.name)
@@ -102,20 +131,26 @@ export class InstanceEffects {
         )
     );
 
-    deleteInstanceSuccess$ = createEffect(() => 
+    /**
+     * Displays remove instance success notification.
+     */
+    deleteInstanceSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.deleteInstanceSuccess),
             tap(() => this.toastr.success('Instance successfully deleted', 'The existing instance has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteInstanceFail$ = createEffect(() => 
+    /**
+     * Displays remove instance fail notification.
+     */
+    deleteInstanceFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(instanceActions.deleteInstanceFail),
             tap(() => this.toastr.error('Failure to delete instance', 'The existing instance could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private instanceService: InstanceService,
diff --git a/client/src/app/metamodel/effects/output-category.effects.spec.ts b/client/src/app/metamodel/effects/output-category.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..770166a623856441a5c7b0f17b073cdbe55c91c3
--- /dev/null
+++ b/client/src/app/metamodel/effects/output-category.effects.spec.ts
@@ -0,0 +1,307 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { ToastrService } from 'ngx-toastr';
+import { OutputCategoryEffects } from './output-category.effects';
+import { OutputCategoryService } from '../services/output-category.service';
+import * as outputCategoryActions from '../actions/output-category.actions';
+import * as datasetSelector from '../selectors/dataset.selector';
+import { CATEGORY, CATEGORY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] OutputCategoryEffects', () => {
+    let actions = new Observable();
+    let effects: OutputCategoryEffects;
+    let metadata: EffectsMetadata<OutputCategoryEffects>;
+    let service: OutputCategoryService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                OutputCategoryEffects,
+                { provide: OutputCategoryService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(OutputCategoryEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(OutputCategoryService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadOutputCategories$ effect', () => {
+        it('should dispatch the loadOutputCategoryListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = outputCategoryActions.loadOutputCategoryList();
+            const outcome = outputCategoryActions.loadOutputCategoryListSuccess({ outputCategories: CATEGORY_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CATEGORY_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveOutputCategoryList = jest.fn(() => response);
+
+            expect(effects.loadOutputCategories$).toBeObservable(expected);
+            expect(service.retrieveOutputCategoryList).toHaveBeenCalledWith('myDataset');
+        });
+
+        it('should dispatch the loadOutputCategoryListFail action on HTTP failure', () => {
+            const action = outputCategoryActions.loadOutputCategoryList();
+            const error = new Error();
+            const outcome = outputCategoryActions.loadOutputCategoryListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveOutputCategoryList = jest.fn(() => response);
+
+            expect(effects.loadOutputCategories$).toBeObservable(expected);
+        });
+    });
+
+    describe('addOutputCategory$ effect', () => {
+        it('should dispatch the addOutputCategorySuccess action on success', () => {
+            const action = outputCategoryActions.addOutputCategory({ outputCategory: CATEGORY });
+            const outcome = outputCategoryActions.addOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CATEGORY });
+            const expected = cold('--b', { b: outcome });
+            service.addOutputCategory = jest.fn(() => response);
+
+            expect(effects.addOutputCategory$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addOutputCategoryFail action on HTTP failure', () => {
+            const action = outputCategoryActions.addOutputCategory({ outputCategory: CATEGORY });
+            const error = new Error();
+            const outcome = outputCategoryActions.addOutputCategoryFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addOutputCategory = jest.fn(() => response);
+
+            expect(effects.addOutputCategory$).toBeObservable(expected);
+        });
+    });
+
+    describe('addOutputCategorySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addOutputCategorySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputCategoryActions.addOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addOutputCategorySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output category successfully added',
+                'The new output category was added into the database'
+            );
+        });
+    });
+
+    describe('addOutputCategoryFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addOutputCategoryFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputCategoryActions.addOutputCategoryFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addOutputCategoryFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add output category',
+                'The new output category could not be added into the database'
+            );
+        });
+    });
+
+    describe('editOutputCategory$ effect', () => {
+        it('should dispatch the editOutputCategorySuccess action on success', () => {
+            const action = outputCategoryActions.editOutputCategory({ outputCategory: CATEGORY });
+            const outcome = outputCategoryActions.editOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CATEGORY });
+            const expected = cold('--b', { b: outcome });
+            service.editOutputCategory = jest.fn(() => response);
+
+            expect(effects.editOutputCategory$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editOutputCategoryFail action on HTTP failure', () => {
+            const action = outputCategoryActions.editOutputCategory({ outputCategory: CATEGORY });
+            const error = new Error();
+            const outcome = outputCategoryActions.editOutputCategoryFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editOutputCategory = jest.fn(() => response);
+
+            expect(effects.editOutputCategory$).toBeObservable(expected);
+        });
+    });
+
+    describe('editOutputCategorySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editOutputCategorySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputCategoryActions.editOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editOutputCategorySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output category successfully edited',
+                'The existing output category has been edited into the database'
+            );
+        });
+    });
+
+    describe('editOutputCategoryFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editOutputCategoryFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputCategoryActions.editOutputCategoryFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editOutputCategoryFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit output category',
+                'The existing output category could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteOutputCategory$ effect', () => {
+        it('should dispatch the deleteOutputCategorySuccess action on success', () => {
+            const action = outputCategoryActions.deleteOutputCategory({ outputCategory: CATEGORY });
+            const outcome = outputCategoryActions.deleteOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: CATEGORY });
+            const expected = cold('--b', { b: outcome });
+            service.deleteOutputCategory = jest.fn(() => response);
+
+            expect(effects.deleteOutputCategory$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteOutputFamilyFail action on HTTP failure', () => {
+            const action = outputCategoryActions.deleteOutputCategory({ outputCategory: CATEGORY });
+            const error = new Error();
+            const outcome = outputCategoryActions.deleteOutputCategoryFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteOutputCategory = jest.fn(() => response);
+
+            expect(effects.deleteOutputCategory$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteOutputCategorySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteOutputCategorySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputCategoryActions.deleteOutputCategorySuccess({ outputCategory: CATEGORY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteOutputCategorySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output category successfully deleted',
+                'The existing output category has been deleted'
+            );
+        });
+    });
+
+    describe('deleteOutputCategoryFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteOutputCategoryFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputCategoryActions.deleteOutputCategoryFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteOutputCategoryFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete output category',
+                'The existing output category could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/output-category.effects.ts b/client/src/app/metamodel/effects/output-category.effects.ts
index a785cc0879b1958996de1cca8d3427d8b31afd4f..eb1278ad006de76d73460134f76bc7bd985daf96 100644
--- a/client/src/app/metamodel/effects/output-category.effects.ts
+++ b/client/src/app/metamodel/effects/output-category.effects.ts
@@ -18,10 +18,18 @@ import { ToastrService } from 'ngx-toastr';
 import * as outputCategoryActions from '../actions/output-category.actions';
 import { OutputCategoryService } from '../services/output-category.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+/**
+ * @class
+ * @classdesc Output category effects.
+ */
 @Injectable()
 export class OutputCategoryEffects {
-    loadOutputCategorys$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve output categories list.
+     */
+    loadOutputCategories$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputCategoryActions.loadOutputCategoryList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -34,7 +42,10 @@ export class OutputCategoryEffects {
         )
     );
 
-    addOutputCategory$ = createEffect(() => 
+    /**
+     * Calls action to add an output category.
+     */
+    addOutputCategory$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputCategoryActions.addOutputCategory),
             mergeMap(action => this.outputCategoryService.addOutputCategory(action.outputCategory)
@@ -46,21 +57,30 @@ export class OutputCategoryEffects {
         )
     );
 
-    addOutputCategorySuccess$ = createEffect(() => 
+    /**
+     * Displays add output category success notification.
+     */
+    addOutputCategorySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.addOutputCategorySuccess),
             tap(() => this.toastr.success('Output category successfully added', 'The new output category was added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addOutputCategoryFail$ = createEffect(() => 
+    /**
+     * Displays add output category fail notification.
+     */
+    addOutputCategoryFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.addOutputCategoryFail),
             tap(() => this.toastr.error('Failure to add output category', 'The new output category could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editOutputCategory$ = createEffect(() => 
+    /**
+     * Calls action to modify an output category.
+     */
+    editOutputCategory$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputCategoryActions.editOutputCategory),
             mergeMap(action => this.outputCategoryService.editOutputCategory(action.outputCategory)
@@ -72,21 +92,30 @@ export class OutputCategoryEffects {
         )
     );
 
-    editOutputCategorySuccess$ = createEffect(() => 
+    /**
+     * Displays modify output category success notification.
+     */
+    editOutputCategorySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.editOutputCategorySuccess),
             tap(() => this.toastr.success('Output category successfully edited', 'The existing output category has been edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editOutputCategoryFail$ = createEffect(() => 
+    /**
+     * Displays modify output category fail notification.
+     */
+    editOutputCategoryFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.editOutputCategoryFail),
             tap(() => this.toastr.error('Failure to edit output category', 'The existing output category could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteOutputCategory$ = createEffect(() => 
+    /**
+     * Calls action to remove an output category.
+     */
+    deleteOutputCategory$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputCategoryActions.deleteOutputCategory),
             mergeMap(action => this.outputCategoryService.deleteOutputCategory(action.outputCategory.id)
@@ -98,20 +127,26 @@ export class OutputCategoryEffects {
         )
     );
 
-    deleteOutputCategorySuccess$ = createEffect(() => 
+    /**
+     * Displays remove output category success notification.
+     */
+    deleteOutputCategorySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.deleteOutputCategorySuccess),
             tap(() => this.toastr.success('Output category successfully deleted', 'The existing output category has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteOutputCategoryFail$ = createEffect(() => 
+    /**
+     * Displays remove output category fail notification.
+     */
+    deleteOutputCategoryFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputCategoryActions.deleteOutputCategoryFail),
             tap(() => this.toastr.error('Failure to delete output category', 'The existing output category could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private outputCategoryService: OutputCategoryService,
diff --git a/client/src/app/metamodel/effects/output-family.effects.spec.ts b/client/src/app/metamodel/effects/output-family.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..267056d81f2d00f9ef42926d741237c1fd140f9a
--- /dev/null
+++ b/client/src/app/metamodel/effects/output-family.effects.spec.ts
@@ -0,0 +1,312 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { OutputFamilyEffects } from './output-family.effects';
+import { OutputFamilyService } from '../services/output-family.service';
+import * as outputFamilyActions from '../actions/output-family.actions';
+import { OUTPUT_FAMILY, OUTPUT_FAMILY_LIST } from '../../../test-data';
+import * as datasetSelector from '../selectors/dataset.selector';
+
+describe('[Metamodel][Effects] OutputFamilyEffects', () => {
+    let actions = new Observable();
+    let effects: OutputFamilyEffects;
+    let metadata: EffectsMetadata<OutputFamilyEffects>;
+    let service: OutputFamilyService;
+    let toastr: ToastrService;
+    let store: MockStore;
+    const initialState = { metamodel: { } };
+    let mockDatasetSelectorSelectDatasetNameByRoute;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                OutputFamilyEffects,
+                { provide: OutputFamilyService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                        success: jest.fn(),
+                        error: jest.fn()
+                    }},
+                provideMockActions(() => actions),
+                provideMockStore({ initialState })
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(OutputFamilyEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(OutputFamilyService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+        store = TestBed.inject(MockStore);
+        mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+            datasetSelector.selectDatasetNameByRoute,''
+        );
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadOutputFamilies$ effect', () => {
+        it('should dispatch the loadOutputFamilyListSuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = outputFamilyActions.loadOutputFamilyList();
+            const outcome = outputFamilyActions.loadOutputFamilyListSuccess({ outputFamilies: OUTPUT_FAMILY_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: OUTPUT_FAMILY_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveOutputFamilyList = jest.fn(() => response);
+
+            expect(effects.loadOutputFamilies$).toBeObservable(expected);
+            expect(service.retrieveOutputFamilyList).toHaveBeenCalledWith('myDataset');
+        });
+
+        it('should dispatch the loadOutputFamilyListFail action on HTTP failure', () => {
+            const action = outputFamilyActions.loadOutputFamilyList();
+            const error = new Error();
+            const outcome = outputFamilyActions.loadOutputFamilyListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveOutputFamilyList = jest.fn(() => response);
+
+            expect(effects.loadOutputFamilies$).toBeObservable(expected);
+        });
+    });
+
+    describe('addOutputFamily$ effect', () => {
+        it('should dispatch the addOutputFamilySuccess action on success', () => {
+            mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
+                datasetSelector.selectDatasetNameByRoute, 'myDataset'
+            );
+
+            const action = outputFamilyActions.addOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const outcome = outputFamilyActions.addOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: OUTPUT_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.addOutputFamily = jest.fn(() => response);
+
+            expect(effects.addOutputFamily$).toBeObservable(expected);
+            expect(service.addOutputFamily).toHaveBeenCalledWith('myDataset', OUTPUT_FAMILY);
+        });
+
+        it('should dispatch the addOutputFamilyFail action on HTTP failure', () => {
+            const action = outputFamilyActions.addOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const error = new Error();
+            const outcome = outputFamilyActions.addOutputFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addOutputFamily = jest.fn(() => response);
+
+            expect(effects.addOutputFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('addOutputFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addOutputFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputFamilyActions.addOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addOutputFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output family successfully added',
+                'The new output family was added into the database'
+            );
+        });
+    });
+
+    describe('addOutputFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addOutputFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputFamilyActions.addOutputFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addOutputFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add output family',
+                'The new output family could not be added into the database'
+            );
+        });
+    });
+
+    describe('editOutputFamily$ effect', () => {
+        it('should dispatch the editOutputFamilySuccess action on success', () => {
+            const action = outputFamilyActions.editOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const outcome = outputFamilyActions.editOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: OUTPUT_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.editOutputFamily = jest.fn(() => response);
+
+            expect(effects.editOutputFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editOutputFamilyFail action on HTTP failure', () => {
+            const action = outputFamilyActions.editOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const error = new Error();
+            const outcome = outputFamilyActions.editOutputFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editOutputFamily = jest.fn(() => response);
+
+            expect(effects.editOutputFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('editOutputFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editOutputFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputFamilyActions.editOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editOutputFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output family successfully edited',
+                'The existing output family has been edited into the database'
+            );
+        });
+    });
+
+    describe('editOutputFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editOutputFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputFamilyActions.editOutputFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editOutputFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit output family',
+                'The existing output family could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteOutputFamily$ effect', () => {
+        it('should dispatch the deleteSurveySuccess action on success', () => {
+            const action = outputFamilyActions.deleteOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const outcome = outputFamilyActions.deleteOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: OUTPUT_FAMILY });
+            const expected = cold('--b', { b: outcome });
+            service.deleteOutputFamily = jest.fn(() => response);
+
+            expect(effects.deleteOutputFamily$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteOutputFamilyFail action on HTTP failure', () => {
+            const action = outputFamilyActions.deleteOutputFamily({ outputFamily: OUTPUT_FAMILY });
+            const error = new Error();
+            const outcome = outputFamilyActions.deleteOutputFamilyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteOutputFamily = jest.fn(() => response);
+
+            expect(effects.deleteOutputFamily$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteOutputFamilySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteOutputFamilySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const spy = jest.spyOn(toastr, 'success');
+            const action = outputFamilyActions.deleteOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteOutputFamilySuccess$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Output family successfully deleted',
+                'The existing output family has been deleted'
+            );
+        });
+    });
+
+    describe('deleteOutputFamilyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteOutputFamilyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = outputFamilyActions.deleteOutputFamilyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteOutputFamilyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete output family',
+                'The existing output family could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/output-family.effects.ts b/client/src/app/metamodel/effects/output-family.effects.ts
index fe65f5d7c92808a43c095dbbbf3fb468050d4813..dfe82c0babd7a66cc87862c7678ec14bf5e4ea03 100644
--- a/client/src/app/metamodel/effects/output-family.effects.ts
+++ b/client/src/app/metamodel/effects/output-family.effects.ts
@@ -18,10 +18,18 @@ import { ToastrService } from 'ngx-toastr';
 import * as outputFamilyActions from '../actions/output-family.actions';
 import { OutputFamilyService } from '../services/output-family.service';
 import * as datasetSelector from '../selectors/dataset.selector';
- 
+
+/**
+ * @class
+ * @classdesc Output family effects.
+ */
 @Injectable()
 export class OutputFamilyEffects {
-    loadOutputFamilies$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve output family list for the given dataset.
+     */
+    loadOutputFamilies$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputFamilyActions.loadOutputFamilyList),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -34,7 +42,10 @@ export class OutputFamilyEffects {
         )
     );
 
-    addOutputFamilies$ = createEffect(() => 
+    /**
+     * Calls action to add an output family to the given dataset.
+     */
+    addOutputFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputFamilyActions.addOutputFamily),
             concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)),
@@ -47,21 +58,30 @@ export class OutputFamilyEffects {
         )
     );
 
-    addOutputFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays add output family success notification.
+     */
+    addOutputFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.addOutputFamilySuccess),
             tap(() => this.toastr.success('Output family successfully added', 'The new output family was added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addOutputFamilyFail$ = createEffect(() => 
+    /**
+     * Displays add output family error notification.
+     */
+    addOutputFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.addOutputFamilyFail),
             tap(() => this.toastr.error('Failure to add output family', 'The new output family could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editOutputFamily$ = createEffect(() => 
+    /**
+     * Calls action to modify an output family.
+     */
+    editOutputFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputFamilyActions.editOutputFamily),
             mergeMap(action => this.outputFamilyService.editOutputFamily(action.outputFamily)
@@ -73,21 +93,31 @@ export class OutputFamilyEffects {
         )
     );
 
-    editOutputFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays edit output family success notification.
+     */
+    editOutputFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.editOutputFamilySuccess),
             tap(() => this.toastr.success('Output family successfully edited', 'The existing output family has been edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editOutputFamilyFail$ = createEffect(() => 
+    /**
+     * Displays edit output family error notification.
+     */
+    editOutputFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.editOutputFamilyFail),
             tap(() => this.toastr.error('Failure to edit output family', 'The existing output family could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteOutputFamily$ = createEffect(() => 
+
+    /**
+     * Calls action to remove an output family.
+     */
+    deleteOutputFamily$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(outputFamilyActions.deleteOutputFamily),
             mergeMap(action => this.outputFamilyService.deleteOutputFamily(action.outputFamily.id)
@@ -99,20 +129,26 @@ export class OutputFamilyEffects {
         )
     );
 
-    deleteOutputFamilySuccess$ = createEffect(() => 
+    /**
+     * Displays delete output family success notification.
+     */
+    deleteOutputFamilySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.deleteOutputFamilySuccess),
             tap(() => this.toastr.success('Output family successfully deleted', 'The existing output family has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteOutputFamilyFail$ = createEffect(() => 
+    /**
+     * Displays delete output family error notification.
+     */
+    deleteOutputFamilyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(outputFamilyActions.deleteOutputFamilyFail),
             tap(() => this.toastr.error('Failure to delete output family', 'The existing output family could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private outputFamilyService: OutputFamilyService,
diff --git a/client/src/app/metamodel/effects/root-directory.effects.spec.ts b/client/src/app/metamodel/effects/root-directory.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2704c84e1b90a55c0b32d8aedfbdad65ab5230e
--- /dev/null
+++ b/client/src/app/metamodel/effects/root-directory.effects.spec.ts
@@ -0,0 +1,62 @@
+import { TestBed } from '@angular/core/testing';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { RootDirectoryEffects } from './root-directory.effects';
+import { RootDirectoryService } from '../services/root-directory.service';
+import * as rootDirectoryActions from '../actions/root-directory.actions';
+import { FILES } from '../../../test-data';
+
+describe('[Metamodel][Effects] RootDirectoryEffects', () => {
+    let actions = new Observable();
+    let effects: RootDirectoryEffects;
+    let metadata: EffectsMetadata<RootDirectoryEffects>;
+    let service: RootDirectoryService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                RootDirectoryEffects,
+                { provide: RootDirectoryService, useValue: { }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(RootDirectoryEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(RootDirectoryService);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadRootDirectory$ effect', () => {
+        it('should dispatch the loadRootDirectorySuccess action on success', () => {
+            const action = rootDirectoryActions.loadRootDirectory({ path: 'path' });
+            const outcome = rootDirectoryActions.loadRootDirectorySuccess({ files: FILES });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: FILES });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveRootDirectory = jest.fn(() => response);
+
+            expect(effects.loadRootDirectory$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadRootDirectoryFail action on HTTP failure', () => {
+            const action = rootDirectoryActions.loadRootDirectory({ path: 'path' });
+            const error = new Error();
+            const outcome = rootDirectoryActions.loadRootDirectoryFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveRootDirectory = jest.fn(() => response);
+
+            expect(effects.loadRootDirectory$).toBeObservable(expected);
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/root-directory.effects.ts b/client/src/app/metamodel/effects/root-directory.effects.ts
index 4c7ed37d6b12f83180bf65a978e8e7d01cae1ae4..f8ff1ff7f5167b651e47b1cea970ed6634808432 100644
--- a/client/src/app/metamodel/effects/root-directory.effects.ts
+++ b/client/src/app/metamodel/effects/root-directory.effects.ts
@@ -15,10 +15,18 @@ import { map, mergeMap, catchError } from 'rxjs/operators';
 
 import * as rootDirectoryActions from '../actions/root-directory.actions';
 import { RootDirectoryService } from '../services/root-directory.service';
- 
+
+/**
+ * @class
+ * @classdesc Root directory effects.
+ */
 @Injectable()
 export class RootDirectoryEffects {
-    loadRootDirectory$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve file list for the given root directory.
+     */
+    loadRootDirectory$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(rootDirectoryActions.loadRootDirectory),
             mergeMap(action => this.rootDirectoryService.retrieveRootDirectory(action.path)
@@ -29,7 +37,7 @@ export class RootDirectoryEffects {
             )
         )
     );
- 
+
     constructor(
         private actions$: Actions,
         private rootDirectoryService: RootDirectoryService
diff --git a/client/src/app/metamodel/effects/select-option.effects.spec.ts b/client/src/app/metamodel/effects/select-option.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33eced9420105607bab0334566560ecd2c18cb5c
--- /dev/null
+++ b/client/src/app/metamodel/effects/select-option.effects.spec.ts
@@ -0,0 +1,288 @@
+import { TestBed } from '@angular/core/testing';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { ToastrService } from 'ngx-toastr';
+import { SelectOptionEffects } from './select-option.effects';
+import { SelectOptionService } from '../services/select-option.service';
+import * as selectOptionActions from '../actions/select-option.actions';
+import { SELECT_OPTION, SELECT_OPTION_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] SelectOptionEffects', () => {
+    let actions = new Observable();
+    let effects: SelectOptionEffects;
+    let metadata: EffectsMetadata<SelectOptionEffects>;
+    let service: SelectOptionService;
+    let toastr: ToastrService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                SelectOptionEffects,
+                { provide: SelectOptionService, useValue: { }},
+                { provide: ToastrService, useValue: {
+                    success: jest.fn(),
+                    error: jest.fn()
+                }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(SelectOptionEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(SelectOptionService);
+        toastr = TestBed.inject(ToastrService);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadSelectOptions$ effect', () => {
+        it('should dispatch the loadSelectOptionListSuccess action on success', () => {
+            const action = selectOptionActions.loadSelectOptionList();
+            const outcome = selectOptionActions.loadSelectOptionListSuccess({ selectOptions: SELECT_OPTION_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT_OPTION_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSelectOptionList = jest.fn(() => response);
+
+            expect(effects.loadSelectOptions$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadSelectOptionListFail action on HTTP failure', () => {
+            const action = selectOptionActions.loadSelectOptionList();
+            const error = new Error();
+            const outcome = selectOptionActions.loadSelectOptionListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSelectOptionList = jest.fn(() => response);
+
+            expect(effects.loadSelectOptions$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSelectOption$ effect', () => {
+        it('should dispatch the addSelectOptionSuccess action on success', () => {
+            const action = selectOptionActions.addSelectOption({ selectOption: SELECT_OPTION });
+            const outcome = selectOptionActions.addSelectOptionSuccess({ selectOption: SELECT_OPTION});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT_OPTION });
+            const expected = cold('--b', { b: outcome });
+            service.addSelectOption = jest.fn(() => response);
+
+            expect(effects.addSelectOption$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addSelectOptionFail action on HTTP failure', () => {
+            const action = selectOptionActions.addSelectOption({ selectOption: SELECT_OPTION });
+            const error = new Error();
+            const outcome = selectOptionActions.addSelectOptionFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addSelectOption = jest.fn(() => response);
+
+            expect(effects.addSelectOption$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSelectOptionSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSelectOptionSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectOptionActions.addSelectOptionSuccess({ selectOption: SELECT_OPTION });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSelectOptionSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select option successfully added',
+                'The new select option was added into the database'
+            );
+        });
+    });
+
+    describe('addSelectOptionFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSelectOptionFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectOptionActions.addSelectOptionFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSelectOptionFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add select option',
+                'The new select option could not be added into the database'
+            );
+        });
+    });
+
+    describe('editSelectOption$ effect', () => {
+        it('should dispatch the editSelectOptionSuccess action on success', () => {
+            const action = selectOptionActions.editSelectOption({ selectOption: SELECT_OPTION });
+            const outcome = selectOptionActions.editSelectOptionSuccess({ selectOption: SELECT_OPTION});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT_OPTION });
+            const expected = cold('--b', { b: outcome });
+            service.editSelectOption = jest.fn(() => response);
+
+            expect(effects.editSelectOption$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editSelectOptionFail action on HTTP failure', () => {
+            const action = selectOptionActions.editSelectOption({ selectOption: SELECT_OPTION });
+            const error = new Error();
+            const outcome = selectOptionActions.editSelectOptionFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editSelectOption = jest.fn(() => response);
+
+            expect(effects.editSelectOption$).toBeObservable(expected);
+        });
+    });
+
+    describe('editSelectOptionSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSelectOptionSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectOptionActions.editSelectOptionSuccess({ selectOption: SELECT_OPTION });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSelectOptionSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select option successfully edited',
+                'The existing select option has been edited into the database'
+            );
+        });
+    });
+
+    describe('editSelectOptionFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSelectOptionFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectOptionActions.editSelectOptionFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSelectOptionFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit select option',
+                'The existing select option could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteSelectOption$ effect', () => {
+        it('should dispatch the deleteSelectOptionSuccess action on success', () => {
+            const action = selectOptionActions.deleteSelectOption({ selectOption: SELECT_OPTION });
+            const outcome = selectOptionActions.deleteSelectOptionSuccess({ selectOption: SELECT_OPTION});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT_OPTION });
+            const expected = cold('--b', { b: outcome });
+            service.deleteSelectOption = jest.fn(() => response);
+
+            expect(effects.deleteSelectOption$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteSelectOptionFail action on HTTP failure', () => {
+            const action = selectOptionActions.deleteSelectOption({ selectOption: SELECT_OPTION });
+            const error = new Error();
+            const outcome = selectOptionActions.deleteSelectOptionFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteSelectOption = jest.fn(() => response);
+
+            expect(effects.deleteSelectOption$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteSelectOptionSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSelectOptionSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectOptionActions.deleteSelectOptionSuccess({ selectOption: SELECT_OPTION });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSelectOptionSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select option successfully deleted',
+                'The existing select option has been deleted'
+            );
+        });
+    });
+
+    describe('deleteSelectOptionFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSelectOptionFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectOptionActions.deleteSelectOptionFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSelectOptionFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete select option',
+                'The existing select option could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/select-option.effects.ts b/client/src/app/metamodel/effects/select-option.effects.ts
index eb98f8546f0da591b95a8f7827ba69492926a885..2beaf5ae6a83a22aa6e589f4f79a1081c6c3ae7e 100644
--- a/client/src/app/metamodel/effects/select-option.effects.ts
+++ b/client/src/app/metamodel/effects/select-option.effects.ts
@@ -16,11 +16,18 @@ import { ToastrService } from 'ngx-toastr';
 
 import * as selectOptionActions from '../actions/select-option.actions';
 import { SelectOptionService } from '../services/select-option.service';
- 
+
+/**
+ * @class
+ * @classdesc Select option effects.
+ */
 @Injectable()
 export class SelectOptionEffects {
- 
-    loadSelectOptions$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve select option list.
+     */
+    loadSelectOptions$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectOptionActions.loadSelectOptionList),
             mergeMap(() => this.selectOptionService.retrieveSelectOptionList()
@@ -31,8 +38,11 @@ export class SelectOptionEffects {
             )
         )
     );
- 
-    addSelectOption$ = createEffect(() => 
+
+    /**
+     * Calls action to add a select option.
+     */
+    addSelectOption$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectOptionActions.addSelectOption),
             mergeMap(action => this.selectOptionService.addSelectOption(action.selectOption)
@@ -44,21 +54,30 @@ export class SelectOptionEffects {
         )
     );
 
-    addSelectOptionSuccess$ = createEffect(() => 
+    /**
+     * Displays add select option success notification.
+     */
+    addSelectOptionSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.addSelectOptionSuccess),
             tap(() => this.toastr.success('Select option successfully added', 'The new select option was added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addSelectOptionFail$ = createEffect(() => 
+    /**
+     * Displays add select option error notification.
+     */
+    addSelectOptionFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.addSelectOptionFail),
             tap(() => this.toastr.error('Failure to add select option', 'The new select option could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSelectOption$ = createEffect(() => 
+    /**
+     * Calls action to modify a select option.
+     */
+    editSelectOption$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectOptionActions.editSelectOption),
             mergeMap(action => this.selectOptionService.editSelectOption(action.selectOption)
@@ -70,21 +89,30 @@ export class SelectOptionEffects {
         )
     );
 
-    editSelectOptionSuccess$ = createEffect(() => 
+    /**
+     * Displays edit select option success notification.
+     */
+    editSelectOptionSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.editSelectOptionSuccess),
             tap(() => this.toastr.success('Select option successfully edited', 'The existing select option has been edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSelectOptionFail$ = createEffect(() => 
+    /**
+     * Displays edit select option error notification.
+     */
+    editSelectOptionFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.editSelectOptionFail),
             tap(() => this.toastr.error('Failure to edit select option', 'The existing select option could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSelectOption$ = createEffect(() => 
+    /**
+     * Calls action to remove a select option.
+     */
+    deleteSelectOption$ = createEffect(():any =>
         this.actions$.pipe(
             ofType(selectOptionActions.deleteSelectOption),
             mergeMap(action => this.selectOptionService.deleteSelectOption(action.selectOption.id)
@@ -96,18 +124,24 @@ export class SelectOptionEffects {
         )
     );
 
-    deleteSelectOptionSuccess$ = createEffect(() => 
+    /**
+     * Displays delete select option success notification.
+     */
+    deleteSelectOptionSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.deleteSelectOptionSuccess),
             tap(() => this.toastr.success('Select option successfully deleted', 'The existing select option has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSelectOptionFail$ = createEffect(() => 
+    /**
+     * Displays delete select option error notification.
+     */
+    deleteSelectOptionFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectOptionActions.deleteSelectOptionFail),
             tap(() => this.toastr.error('Failure to delete select option', 'The existing select option could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
     constructor(
diff --git a/client/src/app/metamodel/effects/select.effects.spec.ts b/client/src/app/metamodel/effects/select.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ccc7d77fadc12a0bd57c97708da484eba6a4cca
--- /dev/null
+++ b/client/src/app/metamodel/effects/select.effects.spec.ts
@@ -0,0 +1,288 @@
+import { TestBed } from '@angular/core/testing';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { ToastrService } from 'ngx-toastr';
+import { SelectEffects } from './select.effects';
+import { SelectService } from '../services/select.service';
+import * as selectActions from '../actions/select.actions';
+import { SELECT, SELECT_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] SelectEffects', () => {
+    let actions = new Observable();
+    let effects: SelectEffects;
+    let metadata: EffectsMetadata<SelectEffects>;
+    let service: SelectService;
+    let toastr: ToastrService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                SelectEffects,
+                { provide: SelectService, useValue: { }},
+                { provide: ToastrService, useValue: {
+                    success: jest.fn(),
+                    error: jest.fn()
+                }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(SelectEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(SelectService);
+        toastr = TestBed.inject(ToastrService);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadSelects$ effect', () => {
+        it('should dispatch the loadSelectListSuccess action on success', () => {
+            const action = selectActions.loadSelectList();
+            const outcome = selectActions.loadSelectListSuccess({ selects: SELECT_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSelectList = jest.fn(() => response);
+
+            expect(effects.loadSelects$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadSelectListFail action on HTTP failure', () => {
+            const action = selectActions.loadSelectList();
+            const error = new Error();
+            const outcome = selectActions.loadSelectListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSelectList = jest.fn(() => response);
+
+            expect(effects.loadSelects$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSelect$ effect', () => {
+        it('should dispatch the addSelectSuccess action on success', () => {
+            const action = selectActions.addSelect({ select: SELECT });
+            const outcome = selectActions.addSelectSuccess({ select: SELECT});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT });
+            const expected = cold('--b', { b: outcome });
+            service.addSelect = jest.fn(() => response);
+
+            expect(effects.addSelect$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addSelectFail action on HTTP failure', () => {
+            const action = selectActions.addSelect({ select: SELECT });
+            const error = new Error();
+            const outcome = selectActions.addSelectFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addSelect = jest.fn(() => response);
+
+            expect(effects.addSelect$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSelectSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSelectSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectActions.addSelectSuccess({ select: SELECT });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSelectSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select successfully added',
+                'The new select was added into the database'
+            );
+        });
+    });
+
+    describe('addSelectFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSelectFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectActions.addSelectFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSelectFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add select',
+                'The new select could not be added into the database'
+            );
+        });
+    });
+
+    describe('editSelect$ effect', () => {
+        it('should dispatch the editSelectSuccess action on success', () => {
+            const action = selectActions.editSelect({ select: SELECT });
+            const outcome = selectActions.editSelectSuccess({ select: SELECT});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT });
+            const expected = cold('--b', { b: outcome });
+            service.editSelect = jest.fn(() => response);
+
+            expect(effects.editSelect$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editSelectFail action on HTTP failure', () => {
+            const action = selectActions.editSelect({ select: SELECT });
+            const error = new Error();
+            const outcome = selectActions.editSelectFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editSelect = jest.fn(() => response);
+
+            expect(effects.editSelect$).toBeObservable(expected);
+        });
+    });
+
+    describe('editSelectSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSelectSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectActions.editSelectSuccess({ select: SELECT });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSelectSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select successfully edited',
+                'The existing select has been edited into the database'
+            );
+        });
+    });
+
+    describe('editSelectFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSelectFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectActions.editSelectFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSelectFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit select',
+                'The existing select could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteSelect$ effect', () => {
+        it('should dispatch the deleteSelectSuccess action on success', () => {
+            const action = selectActions.deleteSelect({ select: SELECT });
+            const outcome = selectActions.deleteSelectSuccess({ select: SELECT});
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SELECT });
+            const expected = cold('--b', { b: outcome });
+            service.deleteSelect = jest.fn(() => response);
+
+            expect(effects.deleteSelect$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteSelectFail action on HTTP failure', () => {
+            const action = selectActions.deleteSelect({ select: SELECT });
+            const error = new Error();
+            const outcome = selectActions.deleteSelectFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteSelect = jest.fn(() => response);
+
+            expect(effects.deleteSelect$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteSelectSuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSelectSuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = selectActions.deleteSelectSuccess({ select: SELECT });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSelectSuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Select successfully deleted',
+                'The existing select has been deleted'
+            );
+        });
+    });
+
+    describe('deleteSelectFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSelectFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = selectActions.deleteSelectFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSelectFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete select',
+                'The existing select could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/select.effects.ts b/client/src/app/metamodel/effects/select.effects.ts
index b3f53c9151a0cc5aa412113297b53150c800183f..c4a21e361d5b0bc916e862217c1604c05048eae0 100644
--- a/client/src/app/metamodel/effects/select.effects.ts
+++ b/client/src/app/metamodel/effects/select.effects.ts
@@ -16,10 +16,18 @@ import { ToastrService } from 'ngx-toastr';
 
 import * as selectActions from '../actions/select.actions';
 import { SelectService } from '../services/select.service';
- 
+
+/**
+ * @class
+ * @classdesc Select effects.
+ */
 @Injectable()
 export class SelectEffects {
-    loadSelects$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve select list.
+     */
+    loadSelects$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectActions.loadSelectList),
             mergeMap(() => this.selectService.retrieveSelectList()
@@ -31,7 +39,10 @@ export class SelectEffects {
         )
     );
 
-    addSelect$ = createEffect(() => 
+    /**
+     * Calls action to add a select.
+     */
+    addSelect$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectActions.addSelect),
             mergeMap(action => this.selectService.addSelect(action.select)
@@ -43,21 +54,30 @@ export class SelectEffects {
         )
     );
 
-    addSelectSuccess$ = createEffect(() => 
+    /**
+     * Displays add select success notification.
+     */
+    addSelectSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.addSelectSuccess),
             tap(() => this.toastr.success('Select successfully added', 'The new select was added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addSelectFail$ = createEffect(() => 
+    /**
+     * Displays add select error notification.
+     */
+    addSelectFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.addSelectFail),
             tap(() => this.toastr.error('Failure to add select', 'The new select could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSelect$ = createEffect(() => 
+    /**
+     * Calls action to modify a select.
+     */
+    editSelect$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectActions.editSelect),
             mergeMap(action => this.selectService.editSelect(action.select)
@@ -69,21 +89,30 @@ export class SelectEffects {
         )
     );
 
-    editSelectSuccess$ = createEffect(() => 
+    /**
+     * Displays edit select success notification.
+     */
+    editSelectSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.editSelectSuccess),
             tap(() => this.toastr.success('Select successfully edited', 'The existing select has been edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSelectFail$ = createEffect(() => 
+    /**
+     * Displays edit select success notification.
+     */
+    editSelectFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.editSelectFail),
             tap(() => this.toastr.error('Failure to edit select', 'The existing select could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSelect$ = createEffect(() => 
+    /**
+     * Calls action to remove a select.
+     */
+    deleteSelect$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(selectActions.deleteSelect),
             mergeMap(action => this.selectService.deleteSelect(action.select.name)
@@ -95,20 +124,26 @@ export class SelectEffects {
         )
     );
 
-    deleteSelectSuccess$ = createEffect(() => 
+    /**
+     * Displays delete select success notification.
+     */
+    deleteSelectSuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.deleteSelectSuccess),
             tap(() => this.toastr.success('Select successfully deleted', 'The existing select has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSelectFail$ = createEffect(() => 
+    /**
+     * Displays delete select error notification.
+     */
+    deleteSelectFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(selectActions.deleteSelectFail),
             tap(() => this.toastr.error('Failure to delete select', 'The existing select could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private selectService: SelectService,
diff --git a/client/src/app/metamodel/effects/survey.effects.spec.ts b/client/src/app/metamodel/effects/survey.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13ac0bbbc52ec976db97c5330d8c6fa3bc46b9c2
--- /dev/null
+++ b/client/src/app/metamodel/effects/survey.effects.spec.ts
@@ -0,0 +1,298 @@
+import { TestBed } from '@angular/core/testing';
+import { Router } from '@angular/router';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+import { ToastrService } from 'ngx-toastr';
+
+import { SurveyEffects } from './survey.effects';
+import { SurveyService } from '../services/survey.service';
+import * as surveyActions from '../actions/survey.actions';
+import { SURVEY, SURVEY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Effects] SurveyEffects', () => {
+    let actions = new Observable();
+    let effects: SurveyEffects;
+    let metadata: EffectsMetadata<SurveyEffects>;
+    let service: SurveyService;
+    let toastr: ToastrService;
+    let router: Router;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                SurveyEffects,
+                { provide: SurveyService, useValue: { }},
+                { provide: Router, useValue: { navigate: jest.fn() }},
+                { provide: ToastrService, useValue: {
+                    success: jest.fn(),
+                    error: jest.fn()
+                }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(SurveyEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(SurveyService);
+        toastr = TestBed.inject(ToastrService);
+        router = TestBed.inject(Router);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadSurveys$ effect', () => {
+        it('should dispatch the loadSurveyListSuccess action on success', () => {
+            const action = surveyActions.loadSurveyList();
+            const outcome = surveyActions.loadSurveyListSuccess({ surveys: SURVEY_LIST });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SURVEY_LIST });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSurveyList = jest.fn(() => response);
+
+            expect(effects.loadSurveys$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadSurveyListFail action on HTTP failure', () => {
+            const action = surveyActions.loadSurveyList();
+            const error = new Error();
+            const outcome = surveyActions.loadSurveyListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveSurveyList = jest.fn(() => response);
+
+            expect(effects.loadSurveys$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSurvey$ effect', () => {
+        it('should dispatch the addSurveySuccess action on success', () => {
+            const action = surveyActions.addSurvey({ survey: SURVEY });
+            const outcome = surveyActions.addSurveySuccess({ survey: SURVEY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SURVEY });
+            const expected = cold('--b', { b: outcome });
+            service.addSurvey = jest.fn(() => response);
+
+            expect(effects.addSurvey$).toBeObservable(expected);
+        });
+
+        it('should dispatch the addSurveyFail action on HTTP failure', () => {
+            const action = surveyActions.addSurvey({ survey: SURVEY });
+            const error = new Error();
+            const outcome = surveyActions.addSurveyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.addSurvey = jest.fn(() => response);
+
+            expect(effects.addSurvey$).toBeObservable(expected);
+        });
+    });
+
+    describe('addSurveySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSurveySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = surveyActions.addSurveySuccess({ survey: SURVEY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSurveySuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Survey successfully added',
+                'The new survey was added into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/survey/survey-list']);
+        });
+    });
+
+    describe('addSurveyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.addSurveyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = surveyActions.addSurveyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.addSurveyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to add survey',
+                'The new survey could not be added into the database'
+            );
+        });
+    });
+
+    describe('editSurvey$ effect', () => {
+        it('should dispatch the editSurveySuccess action on success', () => {
+            const action = surveyActions.editSurvey({ survey: SURVEY });
+            const outcome = surveyActions.editSurveySuccess({ survey: SURVEY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SURVEY });
+            const expected = cold('--b', { b: outcome });
+            service.editSurvey = jest.fn(() => response);
+
+            expect(effects.editSurvey$).toBeObservable(expected);
+        });
+
+        it('should dispatch the editSurveyFail action on HTTP failure', () => {
+            const action = surveyActions.editSurvey({ survey: SURVEY });
+            const error = new Error();
+            const outcome = surveyActions.editSurveyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.editSurvey = jest.fn(() => response);
+
+            expect(effects.editSurvey$).toBeObservable(expected);
+        });
+    });
+
+    describe('editSurveySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSurveySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const routerSpy = jest.spyOn(router, 'navigate');
+            const action = surveyActions.editSurveySuccess({ survey: SURVEY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSurveySuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Survey successfully edited',
+                'The existing survey has been edited into the database'
+            );
+            expect(routerSpy).toHaveBeenCalledTimes(1);
+            expect(routerSpy).toHaveBeenCalledWith(['/admin/survey/survey-list']);
+        });
+    });
+
+    describe('editSurveyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.editSurveyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = surveyActions.editSurveyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.editSurveyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to edit survey',
+                'The existing survey could not be edited into the database'
+            );
+        });
+    });
+
+    describe('deleteSurvey$ effect', () => {
+        it('should dispatch the deleteSurveySuccess action on success', () => {
+            const action = surveyActions.deleteSurvey({ survey: SURVEY });
+            const outcome = surveyActions.deleteSurveySuccess({ survey: SURVEY });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: SURVEY });
+            const expected = cold('--b', { b: outcome });
+            service.deleteSurvey = jest.fn(() => response);
+
+            expect(effects.deleteSurvey$).toBeObservable(expected);
+        });
+
+        it('should dispatch the deleteSurveyFail action on HTTP failure', () => {
+            const action = surveyActions.deleteSurvey({ survey: SURVEY });
+            const error = new Error();
+            const outcome = surveyActions.deleteSurveyFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.deleteSurvey = jest.fn(() => response);
+
+            expect(effects.deleteSurvey$).toBeObservable(expected);
+        });
+    });
+
+    describe('deleteSurveySuccess$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSurveySuccess$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display a success notification', () => {
+            const toastrSpy = jest.spyOn(toastr, 'success');
+            const action = surveyActions.deleteSurveySuccess({ survey: SURVEY });
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSurveySuccess$).toBeObservable(expected);
+            expect(toastrSpy).toHaveBeenCalledTimes(1);
+            expect(toastrSpy).toHaveBeenCalledWith(
+                'Survey successfully deleted',
+                'The existing survey has been deleted'
+            );
+        });
+    });
+
+    describe('deleteSurveyFail$ effect', () => {
+        it('should not dispatch', () => {
+            expect(metadata.deleteSurveyFail$).toEqual(
+                expect.objectContaining({ dispatch: false })
+            );
+        });
+
+        it('should display an error notification', () => {
+            const spy = jest.spyOn(toastr, 'error');
+            const action = surveyActions.deleteSurveyFail();
+
+            actions = hot('a', { a: action });
+            const expected = cold('a', { a: action });
+
+            expect(effects.deleteSurveyFail$).toBeObservable(expected);
+            expect(spy).toHaveBeenCalledTimes(1);
+            expect(spy).toHaveBeenCalledWith(
+                'Failure to delete survey',
+                'The existing survey could not be deleted from the database'
+            );
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/survey.effects.ts b/client/src/app/metamodel/effects/survey.effects.ts
index 7d876b5ac2fa0d0f8d87b9b13483e732daddbd76..7d6cc6530d0fd701bf2b82c2fa16d056637fdd5c 100644
--- a/client/src/app/metamodel/effects/survey.effects.ts
+++ b/client/src/app/metamodel/effects/survey.effects.ts
@@ -17,10 +17,18 @@ import { ToastrService } from 'ngx-toastr';
 
 import * as surveyActions from '../actions/survey.actions';
 import { SurveyService } from '../services/survey.service';
- 
+
+/**
+ * @class
+ * @classdesc Survey effects.
+ */
 @Injectable()
 export class SurveyEffects {
-    loadSurveys$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve survey list.
+     */
+    loadSurveys$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(surveyActions.loadSurveyList),
             mergeMap(() => this.surveyService.retrieveSurveyList()
@@ -31,8 +39,11 @@ export class SurveyEffects {
             )
         )
     );
-    
-    addSurvey$ = createEffect(() => 
+
+    /**
+     * Calls action to add survey.
+     */
+    addSurvey$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(surveyActions.addSurvey),
             mergeMap(action => this.surveyService.addSurvey(action.survey)
@@ -44,24 +55,33 @@ export class SurveyEffects {
         )
     );
 
-    addSurveySuccess$ = createEffect(() => 
+    /**
+     * Displays add survey success notification.
+     */
+    addSurveySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.addSurveySuccess),
             tap(() => {
                 this.router.navigate(['/admin/survey/survey-list']);
                 this.toastr.success('Survey successfully added', 'The new survey was added into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    addSurveyFail$ = createEffect(() => 
+    /**
+     * Displays add survey error notification.
+     */
+    addSurveyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.addSurveyFail),
             tap(() => this.toastr.error('Failure to add survey', 'The new survey could not be added into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSurvey$ = createEffect(() => 
+    /**
+     * Calls action to modify a survey.
+     */
+    editSurvey$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(surveyActions.editSurvey),
             mergeMap(action => this.surveyService.editSurvey(action.survey)
@@ -73,24 +93,33 @@ export class SurveyEffects {
         )
     );
 
-    editSurveySuccess$ = createEffect(() => 
+    /**
+     * Displays edit survey success notification.
+     */
+    editSurveySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.editSurveySuccess),
             tap(() => {
                 this.router.navigate(['/admin/survey/survey-list']);
                 this.toastr.success('Survey successfully edited', 'The existing survey has been edited into the database')
             })
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    editSurveyFail$ = createEffect(() => 
+    /**
+     * Displays edit survey error notification.
+     */
+    editSurveyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.editSurveyFail),
             tap(() => this.toastr.error('Failure to edit survey', 'The existing survey could not be edited into the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSurvey$ = createEffect(() => 
+    /**
+     * Calls action to remove a survey.
+     */
+    deleteSurvey$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(surveyActions.deleteSurvey),
             mergeMap(action => this.surveyService.deleteSurvey(action.survey.name)
@@ -102,24 +131,30 @@ export class SurveyEffects {
         )
     );
 
-    deleteSurveySuccess$ = createEffect(() => 
+    /**
+     * Displays delete survey success notification.
+     */
+    deleteSurveySuccess$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.deleteSurveySuccess),
             tap(() => this.toastr.success('Survey successfully deleted', 'The existing survey has been deleted'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
 
-    deleteSurveyFail$ = createEffect(() => 
+    /**
+     * Displays edit survey error notification.
+     */
+    deleteSurveyFail$ = createEffect(() =>
         this.actions$.pipe(
             ofType(surveyActions.deleteSurveyFail),
             tap(() => this.toastr.error('Failure to delete survey', 'The existing survey could not be deleted from the database'))
-        ), { dispatch: false}
+        ), { dispatch: false }
     );
- 
+
     constructor(
         private actions$: Actions,
         private surveyService: SurveyService,
         private router: Router,
         private toastr: ToastrService
-    ) {}
+    ) { }
 }
diff --git a/client/src/app/metamodel/effects/table.effects.spec.ts b/client/src/app/metamodel/effects/table.effects.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..72e7b2ccab9e795032636f9c05c0db49d1bfb481
--- /dev/null
+++ b/client/src/app/metamodel/effects/table.effects.spec.ts
@@ -0,0 +1,61 @@
+import { TestBed } from '@angular/core/testing';
+
+import { provideMockActions } from '@ngrx/effects/testing';
+import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects';
+import { Observable } from 'rxjs';
+import { cold, hot } from 'jasmine-marbles';
+
+import { TableEffects } from './table.effects';
+import { TableService } from '../services/table.service';
+import * as tableActions from '../actions/table.actions';
+
+describe('[Metamodel][Effects] TableEffects', () => {
+    let actions = new Observable();
+    let effects: TableEffects;
+    let metadata: EffectsMetadata<TableEffects>;
+    let service: TableService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                TableEffects,
+                { provide: TableService, useValue: { }},
+                provideMockActions(() => actions)
+            ]
+        }).compileComponents();
+        effects = TestBed.inject(TableEffects);
+        metadata = getEffectsMetadata(effects);
+        service = TestBed.inject(TableService);
+    });
+
+    it('should be created', () => {
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadTables$ effect', () => {
+        it('should dispatch the loadTableListSuccess action on success', () => {
+            const action = tableActions.loadTableList({ idDatabase: 1 });
+            const outcome = tableActions.loadTableListSuccess({ tables: ['table1', 'table2'] });
+
+            actions = hot('-a', { a: action });
+            const response = cold('-a|', { a: ['table1', 'table2'] });
+            const expected = cold('--b', { b: outcome });
+            service.retrieveTableList = jest.fn(() => response);
+
+            expect(effects.loadTables$).toBeObservable(expected);
+        });
+
+        it('should dispatch the loadTableListFail action on HTTP failure', () => {
+            const action = tableActions.loadTableList({ idDatabase: 1 });
+            const error = new Error();
+            const outcome = tableActions.loadTableListFail();
+
+            actions = hot('-a', { a: action });
+            const response = cold('-#|', { }, error);
+            const expected = cold('--b', { b: outcome });
+            service.retrieveTableList = jest.fn(() => response);
+
+            expect(effects.loadTables$).toBeObservable(expected);
+        });
+    });
+});
diff --git a/client/src/app/metamodel/effects/table.effects.ts b/client/src/app/metamodel/effects/table.effects.ts
index 0a32c24095f5a89886a58ec123fed5e4fb0fec05..9a507ce2be574c5d480e6ee67fc286a82b82cc16 100644
--- a/client/src/app/metamodel/effects/table.effects.ts
+++ b/client/src/app/metamodel/effects/table.effects.ts
@@ -15,10 +15,18 @@ import { map, mergeMap, catchError } from 'rxjs/operators';
 
 import * as tableActions from '../actions/table.actions';
 import { TableService } from '../services/table.service';
- 
+
+/**
+ * @class
+ * @classdesc Table effects.
+ */
 @Injectable()
 export class TableEffects {
-    loadTables$ = createEffect(() =>
+
+    /**
+     * Calls action to retrieve table list.
+     */
+    loadTables$ = createEffect((): any =>
         this.actions$.pipe(
             ofType(tableActions.loadTableList),
             mergeMap(action => this.tableService.retrieveTableList(action.idDatabase)
@@ -29,9 +37,9 @@ export class TableEffects {
             )
         )
     );
- 
+
     constructor(
         private actions$: Actions,
         private tableService: TableService
-    ) {}
+    ) { }
 }
diff --git a/client/src/app/metamodel/metamodel.module.ts b/client/src/app/metamodel/metamodel.module.ts
index 9713c0a547f397d0373c60bb134397685f12ad33..a5d06d880ee034d114796b043240cfe3597db362 100644
--- a/client/src/app/metamodel/metamodel.module.ts
+++ b/client/src/app/metamodel/metamodel.module.ts
@@ -17,6 +17,10 @@ import { metamodelReducer } from './metamodel.reducer';
 import { metamodelEffects } from './effects';
 import { metamodelServices} from './services';
 
+/**
+ * @class
+ * @classdesc Metamodel module.
+ */
 @NgModule({
     imports: [
         CommonModule,
diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts
index 6df176d13fce0b8849defbc9de0d4cb327036f93..d7da50592c6ce08bd76d511891f220b6c8c38182 100644
--- a/client/src/app/metamodel/metamodel.reducer.ts
+++ b/client/src/app/metamodel/metamodel.reducer.ts
@@ -27,6 +27,11 @@ import * as select from './reducers/select.reducer';
 import * as selectOption from './reducers/select-option.reducer';
 import * as attributeDistinct from './reducers/attribute-distinct.reducer';
 
+/**
+ * Interface for metamodel state.
+ *
+ * @interface State
+ */
 export interface State {
     database: database.State;
     table: table.State;
@@ -68,4 +73,3 @@ const reducers = {
 export const metamodelReducer = combineReducers(reducers);
 export const getMetamodelState = createFeatureSelector<State>('metamodel');
 export const selectRouterState = createFeatureSelector<RouterReducerState>('router');
- 
\ No newline at end of file
diff --git a/client/src/app/metamodel/models/attribute.model.ts b/client/src/app/metamodel/models/attribute.model.ts
index d5397bb427a5f482e707b2868aa484c3917e7f25..f58acd2c159e59532c9bf8e085b840b170a9b145 100644
--- a/client/src/app/metamodel/models/attribute.model.ts
+++ b/client/src/app/metamodel/models/attribute.model.ts
@@ -8,8 +8,13 @@
  */
 
 import { Option } from './option.model';
-import { RendererConfig } from './renderers/renderer-config.model';
+import { RendererConfig } from './renderers';
 
+/**
+ * Interface for attribute.
+ *
+ * @interface Attribute
+ */
 export interface Attribute {
     id: number;
     name: string;
diff --git a/client/src/app/metamodel/models/column.model.ts b/client/src/app/metamodel/models/column.model.ts
index c06bebf009ba8cab65cc21ed544e5be68ba23cfd..83e1ff2799679a2461e6165a74487344f3b6310b 100644
--- a/client/src/app/metamodel/models/column.model.ts
+++ b/client/src/app/metamodel/models/column.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for column.
+ *
+ * @interface Column
+ */
 export interface Column {
     name: string;
     type: string;
diff --git a/client/src/app/metamodel/models/criteria-family.model.ts b/client/src/app/metamodel/models/criteria-family.model.ts
index aeb7f90aa539d50d73eadad7016bbf1986c4fba1..4870fc29908f90f0011f2391ecd9c528cc4136dd 100644
--- a/client/src/app/metamodel/models/criteria-family.model.ts
+++ b/client/src/app/metamodel/models/criteria-family.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for criteria family.
+ *
+ * @interface CriteriaFamily
+ */
 export interface CriteriaFamily {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/database.model.ts b/client/src/app/metamodel/models/database.model.ts
index e2889a40f945641d8928cbb54000b30ff44cf055..96ce8db984564381c9362c3bc6f7ef09c26cdbf3 100644
--- a/client/src/app/metamodel/models/database.model.ts
+++ b/client/src/app/metamodel/models/database.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for database.
+ *
+ * @interface Database
+ */
 export interface Database {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/dataset-family.model.ts b/client/src/app/metamodel/models/dataset-family.model.ts
index 391f90392ee3e08741ef0b34ff73ad65cdb81569..98ff77f2a92fda884f097227880d648b3b11c5f3 100644
--- a/client/src/app/metamodel/models/dataset-family.model.ts
+++ b/client/src/app/metamodel/models/dataset-family.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for dataset family.
+ *
+ * @interface DatasetFamily
+ */
 export interface DatasetFamily {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/dataset.model.ts b/client/src/app/metamodel/models/dataset.model.ts
index 22e73074318ab24c91fdb32836dd9c3834c8a1ea..7c7baa5d11a922ea9bb4353133a9e0c0a883ca92 100644
--- a/client/src/app/metamodel/models/dataset.model.ts
+++ b/client/src/app/metamodel/models/dataset.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for dataset.
+ *
+ * @interface Dataset
+ */
 export interface Dataset {
     name: string;
     table_ref: string;
diff --git a/client/src/app/metamodel/models/file-info.model.ts b/client/src/app/metamodel/models/file-info.model.ts
index 675c092cfd0cb0b163701fcd37234034f705cce8..f966093e908d5f5a1ce1c72732f3829c9b9d7757 100644
--- a/client/src/app/metamodel/models/file-info.model.ts
+++ b/client/src/app/metamodel/models/file-info.model.ts
@@ -7,6 +7,12 @@
  * file that was distributed with this source code.
  */
 
+
+/**
+ * Interface for file info.
+ *
+ * @interface FileInfo
+ */
 export interface FileInfo {
     name: string;
     size: number;
diff --git a/client/src/app/metamodel/models/group.model.ts b/client/src/app/metamodel/models/group.model.ts
index 788f669b99d768c8edfdc416eb3e2966011d5526..64ed71fea6cdc94abb8f633ff238b16f0b053819 100644
--- a/client/src/app/metamodel/models/group.model.ts
+++ b/client/src/app/metamodel/models/group.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for group.
+ *
+ * @interface Group
+ */
 export interface Group {
     id: number;
     role: string;
diff --git a/client/src/app/metamodel/models/instance.model.ts b/client/src/app/metamodel/models/instance.model.ts
index c50e7bc234b97659b9b950b118669774f02ee582..bbb5436413cfed03eb5fe121d83183fab8d11246 100644
--- a/client/src/app/metamodel/models/instance.model.ts
+++ b/client/src/app/metamodel/models/instance.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for instance.
+ *
+ * @interface Instance
+ */
 export interface Instance {
     name: string;
     label: string;
diff --git a/client/src/app/metamodel/models/option.model.ts b/client/src/app/metamodel/models/option.model.ts
index 3b934e2551fe4e1bcd30278ed5da1201dcf6da26..91ab5ee69236517db59a12fea55da73f3e0ac202 100644
--- a/client/src/app/metamodel/models/option.model.ts
+++ b/client/src/app/metamodel/models/option.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for option.
+ *
+ * @interface Option
+ */
 export interface Option {
     label: string;
     value: string;
diff --git a/client/src/app/metamodel/models/output-category.model.ts b/client/src/app/metamodel/models/output-category.model.ts
index 408d7c8d54b113cb2b19058b7162132109714ab1..637b562ff24d3d1696f4a695d0ef1bb5a519a8f9 100644
--- a/client/src/app/metamodel/models/output-category.model.ts
+++ b/client/src/app/metamodel/models/output-category.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for output category.
+ *
+ * @interface OutputCategory
+ */
 export interface OutputCategory {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/output-family.model.ts b/client/src/app/metamodel/models/output-family.model.ts
index d1d1e076bba1a1bdba4a83c4f48705b8eed3b334..f0970ab3b9155dcbd9c1bb0afecfbec650393633 100644
--- a/client/src/app/metamodel/models/output-family.model.ts
+++ b/client/src/app/metamodel/models/output-family.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for output family.
+ *
+ * @interface OutputFamily
+ */
 export interface OutputFamily {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/renderers/detail-renderer-config.model.ts b/client/src/app/metamodel/models/renderers/detail-renderer-config.model.ts
index 4012f3c47917e6d1624da3be0ab9e98e52c0fc8a..921abadd662842ed0555c99fe5c9ea00ae1818a0 100644
--- a/client/src/app/metamodel/models/renderers/detail-renderer-config.model.ts
+++ b/client/src/app/metamodel/models/renderers/detail-renderer-config.model.ts
@@ -1,5 +1,20 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { RendererConfig } from './renderer-config.model';
 
+/**
+ * Interface for detail renderer config.
+ *
+ * @interface DetailRendererConfig
+ * @extends RendererConfig
+ */
 export interface DetailRendererConfig extends RendererConfig {
     display: string;
     icon_button: string;
diff --git a/client/src/app/metamodel/models/renderers/download-renderer-config.model.ts b/client/src/app/metamodel/models/renderers/download-renderer-config.model.ts
index 93747e6e08d8c1375560448e493f51120005f5a0..c9291889176a2a6997a020295576198094c441a9 100644
--- a/client/src/app/metamodel/models/renderers/download-renderer-config.model.ts
+++ b/client/src/app/metamodel/models/renderers/download-renderer-config.model.ts
@@ -1,5 +1,20 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { RendererConfig } from './renderer-config.model';
 
+/**
+ * Interface for download renderer config.
+ *
+ * @interface DownloadRendererConfig
+ * @extends RendererConfig
+ */
 export interface DownloadRendererConfig extends RendererConfig {
     display: string;
     text: string;
diff --git a/client/src/app/metamodel/models/renderers/image-renderer-config.model.ts b/client/src/app/metamodel/models/renderers/image-renderer-config.model.ts
index a93270e3bcec7f71509cec94a56714ef7058d670..2e6e89efc8495344dd08f04f268d171525d51ad6 100644
--- a/client/src/app/metamodel/models/renderers/image-renderer-config.model.ts
+++ b/client/src/app/metamodel/models/renderers/image-renderer-config.model.ts
@@ -1,5 +1,20 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { RendererConfig } from './renderer-config.model';
 
+/**
+ * Interface for image renderer config.
+ *
+ * @interface ImageRendererConfig
+ * @extends RendererConfig
+ */
 export interface ImageRendererConfig extends RendererConfig {
     type: string;
     display: string;
diff --git a/client/src/app/metamodel/models/renderers/link-renderer-config.model.ts b/client/src/app/metamodel/models/renderers/link-renderer-config.model.ts
index 9c2c296561ae411cc7288e5c7e57d28b46e3bb49..2cb4fc07c1eb2217a32d11f9e74d8ea33f999e18 100644
--- a/client/src/app/metamodel/models/renderers/link-renderer-config.model.ts
+++ b/client/src/app/metamodel/models/renderers/link-renderer-config.model.ts
@@ -1,5 +1,20 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
 import { RendererConfig } from './renderer-config.model';
 
+/**
+ * Interface for link renderer config.
+ *
+ * @interface LinkRendererConfig
+ * @extends RendererConfig
+ */
 export interface LinkRendererConfig extends RendererConfig {
     href: string;
     display: string;
diff --git a/client/src/app/metamodel/models/renderers/renderer-config.model.ts b/client/src/app/metamodel/models/renderers/renderer-config.model.ts
index 00fafcd4a0df22cf3b9efdde9803ed1e5d190c25..cd8d137570dd14e95be1ab28cf25f5707e3d3416 100644
--- a/client/src/app/metamodel/models/renderers/renderer-config.model.ts
+++ b/client/src/app/metamodel/models/renderers/renderer-config.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for renderer config.
+ *
+ * @interface RendererConfig
+ */
 export interface RendererConfig {
     id: 'renderer-config';
 }
diff --git a/client/src/app/metamodel/models/select-option.model.ts b/client/src/app/metamodel/models/select-option.model.ts
index 014a43f253ec46c4f71433f272e77dd7eca42395..bb3473f08f3b095e7b8985fe355acab0587b5799 100644
--- a/client/src/app/metamodel/models/select-option.model.ts
+++ b/client/src/app/metamodel/models/select-option.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for select option.
+ *
+ * @interface SelectOption
+ */
 export interface SelectOption {
     id: number;
     label: string;
diff --git a/client/src/app/metamodel/models/select.model.ts b/client/src/app/metamodel/models/select.model.ts
index c969f18c16d0d5447d0ab3857f88233eee1e3bfe..4bdb3d403ae6b2c5238f4240f11bccba687da673 100644
--- a/client/src/app/metamodel/models/select.model.ts
+++ b/client/src/app/metamodel/models/select.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for select.
+ *
+ * @interface Select
+ */
 export interface Select {
     name: string;
     label: string;
diff --git a/client/src/app/metamodel/models/survey.model.ts b/client/src/app/metamodel/models/survey.model.ts
index bba021fddcc2f97f94b5b25104fb639c27c60fef..c23ab90afca9832f448b6f69ddc457218803d680 100644
--- a/client/src/app/metamodel/models/survey.model.ts
+++ b/client/src/app/metamodel/models/survey.model.ts
@@ -7,6 +7,11 @@
  * file that was distributed with this source code.
  */
 
+/**
+ * Interface for survey.
+ *
+ * @interface Survey
+ */
 export interface Survey {
     name: string;
     label: string;
diff --git a/client/src/app/metamodel/reducers/attribute-distinct.reducer.spec.ts b/client/src/app/metamodel/reducers/attribute-distinct.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e94ea987ae68546de0e171b54972a78a845874d5
--- /dev/null
+++ b/client/src/app/metamodel/reducers/attribute-distinct.reducer.spec.ts
@@ -0,0 +1,63 @@
+import { Action } from '@ngrx/store';
+
+import * as fromAttributeDistinct from './attribute-distinct.reducer';
+import * as attributeDistinctActions from '../actions/attribute-distinct.actions';
+import { ATTRIBUTE, SURVEY, SURVEY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] AttributeDistinct reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromAttributeDistinct;
+        const action = { type: 'Unknown' };
+        const state = fromAttributeDistinct.attributeDistinctReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadAttributeDistinctList action should set attributeDistinctListIsLoading to true', () => {
+        const { initialState } = fromAttributeDistinct;
+        const action = attributeDistinctActions.loadAttributeDistinctList({ attribute: ATTRIBUTE });
+        const state = fromAttributeDistinct.attributeDistinctReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.attributeDistinctListIsLoading).toEqual(true);
+        expect(state.attributeDistinctListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadAttributeDistinctListSuccess action should add attributeDistinct list, set attributeDistinctListIsLoading to false and set attributeDistinctListIsLoaded to true', () => {
+        const { initialState } = fromAttributeDistinct;
+        const action = attributeDistinctActions.loadAttributeDistinctListSuccess({ values: ['val-one', 'val-two'] });
+        const state = fromAttributeDistinct.attributeDistinctReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('val-one');
+        expect(state.ids).toContain('val-two');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.attributeDistinctListIsLoading).toEqual(false);
+        expect(state.attributeDistinctListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadAttributeDistinctListFail action should set attributeDistinctListIsLoading to false', () => {
+        const { initialState } = fromAttributeDistinct;
+        const action = attributeDistinctActions.loadAttributeDistinctListFail();
+        const state = fromAttributeDistinct.attributeDistinctReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.attributeDistinctListIsLoading).toEqual(false);
+        expect(state.attributeDistinctListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get attributeDistinctListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromAttributeDistinct.attributeDistinctReducer(undefined, action);
+
+        expect(fromAttributeDistinct.selectAttributeDistinctListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get attributeDistinctListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromAttributeDistinct.attributeDistinctReducer(undefined, action);
+
+        expect(fromAttributeDistinct.selectAttributeDistinctListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/attribute-distinct.reducer.ts b/client/src/app/metamodel/reducers/attribute-distinct.reducer.ts
index 657d050033e7cb11b6ad3c23023731930c57f393..533d4c31ad6431a8d9906b7999decfde8a8f5755 100644
--- a/client/src/app/metamodel/reducers/attribute-distinct.reducer.ts
+++ b/client/src/app/metamodel/reducers/attribute-distinct.reducer.ts
@@ -12,6 +12,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 
 import * as attributeDistinctActions from '../actions/attribute-distinct.actions';
 
+/**
+ * Interface for attribute distinct state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<string> {
     attributeDistinctListIsLoading: boolean;
     attributeDistinctListIsLoaded: boolean;
@@ -37,8 +42,8 @@ export const attributeDistinctReducer = createReducer(
     }),
     on(attributeDistinctActions.loadAttributeDistinctListSuccess, (state, { values }) => {
         return adapter.setAll(
-            values, 
-            { 
+            values,
+            {
                 ...state,
                 attributeDistinctListIsLoading: false,
                 attributeDistinctListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/attribute.reducer.spec.ts b/client/src/app/metamodel/reducers/attribute.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b1abaad67d767c1afd7f6a8a025a863fb5ba070f
--- /dev/null
+++ b/client/src/app/metamodel/reducers/attribute.reducer.spec.ts
@@ -0,0 +1,109 @@
+import { Action } from '@ngrx/store';
+
+import * as fromAttribute from './attribute.reducer';
+import * as attributeActions from '../actions/attribute.actions';
+import { ATTRIBUTE, ATTRIBUTE_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Attribute reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromAttribute;
+        const action = { type: 'Unknown' };
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadAttributeList action should set attributeListIsLoading to true', () => {
+        const { initialState } = fromAttribute;
+        const action = attributeActions.loadAttributeList();
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.attributeListIsLoading).toEqual(true);
+        expect(state.attributeListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadAttributeListSuccess action should add attribute list, set attributeListIsLoading to false and set attributeListIsLoaded to true', () => {
+        const { initialState } = fromAttribute;
+        const action = attributeActions.loadAttributeListSuccess({ attributes: ATTRIBUTE_LIST });
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(4);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(state.ids).toContain(3);
+        expect(state.ids).toContain(4);
+        expect(Object.keys(state.entities).length).toEqual(4);
+        expect(state.attributeListIsLoading).toEqual(false);
+        expect(state.attributeListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadAttributeListFail action should set attributeListIsLoading to false', () => {
+        const { initialState } = fromAttribute;
+        const action = attributeActions.loadAttributeListFail();
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.attributeListIsLoading).toEqual(false);
+        expect(state.attributeListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addAttributeSuccess action should add a attribute', () => {
+        const { initialState } = fromAttribute;
+        const action = attributeActions.addAttributeSuccess({ attribute: ATTRIBUTE });
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.attributeListIsLoading).toEqual(false);
+        expect(state.attributeListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editAttributeSuccess action should modify a attribute', () => {
+        const initialState = {
+            ...fromAttribute.initialState,
+            ids: [1],
+            entities: { 1: { ...ATTRIBUTE, label: 'label' }}
+        };
+        const action = attributeActions.editAttributeSuccess({ attribute: ATTRIBUTE });
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(ATTRIBUTE);
+        expect(state.attributeListIsLoading).toEqual(false);
+        expect(state.attributeListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteAttributeSuccess action should modify a attribute', () => {
+        const initialState = {
+            ...fromAttribute.initialState,
+            ids: [1],
+            entities: { 1: ATTRIBUTE }
+        };
+        const action = attributeActions.deleteAttributeSuccess({ attribute: ATTRIBUTE });
+        const state = fromAttribute.attributeReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.attributeListIsLoading).toEqual(false);
+        expect(state.attributeListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get attributeListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromAttribute.attributeReducer(undefined, action);
+
+        expect(fromAttribute.selectAttributeListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get attributeListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromAttribute.attributeReducer(undefined, action);
+
+        expect(fromAttribute.selectAttributeListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/attribute.reducer.ts b/client/src/app/metamodel/reducers/attribute.reducer.ts
index 769d7e329f6b32c2cc12e9699f11cb0ad62c8965..3def9fef90535dcb6dcf5875d2b84dd7dbe78ebc 100644
--- a/client/src/app/metamodel/reducers/attribute.reducer.ts
+++ b/client/src/app/metamodel/reducers/attribute.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Attribute } from '../models';
 import * as attributeActions from '../actions/attribute.actions';
 
+/**
+ * Interface for attribute state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Attribute> {
     attributeListIsLoading: boolean;
     attributeListIsLoaded: boolean;
@@ -36,8 +41,8 @@ export const attributeReducer = createReducer(
     }),
     on(attributeActions.loadAttributeListSuccess, (state, { attributes }) => {
         return adapter.setAll(
-            attributes, 
-            { 
+            attributes,
+            {
                 ...state,
                 attributeListIsLoading: false,
                 attributeListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/column.reducer.spec.ts b/client/src/app/metamodel/reducers/column.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69efb358fa86cc72d48707c818a3e3fde47b60fa
--- /dev/null
+++ b/client/src/app/metamodel/reducers/column.reducer.spec.ts
@@ -0,0 +1,63 @@
+import { Action } from '@ngrx/store';
+
+import * as fromColumn from './column.reducer';
+import * as columnActions from '../actions/column.actions';
+import { COLUMN_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Column reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromColumn;
+        const action = { type: 'Unknown' };
+        const state = fromColumn.columnReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadColumnList action should set columnListIsLoading to true', () => {
+        const { initialState } = fromColumn;
+        const action = columnActions.loadColumnList();
+        const state = fromColumn.columnReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.columnListIsLoading).toEqual(true);
+        expect(state.columnListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadColumnListSuccess action should add column list, set columnListIsLoading to false and set columnListIsLoaded to true', () => {
+        const { initialState } = fromColumn;
+        const action = columnActions.loadColumnListSuccess({ columns: COLUMN_LIST });
+        const state = fromColumn.columnReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('myCol');
+        expect(state.ids).toContain('anotherCol');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.columnListIsLoading).toEqual(false);
+        expect(state.columnListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadColumnListFail action should set columnListIsLoading to false', () => {
+        const { initialState } = fromColumn;
+        const action = columnActions.loadColumnListFail();
+        const state = fromColumn.columnReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.columnListIsLoading).toEqual(false);
+        expect(state.columnListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get columnListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromColumn.columnReducer(undefined, action);
+
+        expect(fromColumn.selectColumnListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get columnListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromColumn.columnReducer(undefined, action);
+
+        expect(fromColumn.selectColumnListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/column.reducer.ts b/client/src/app/metamodel/reducers/column.reducer.ts
index ad36e21591ca9513ec60f6a1664ebdf47cb84f50..faee0a628bff04249e65c8e3edf36a8fbe20e010 100644
--- a/client/src/app/metamodel/reducers/column.reducer.ts
+++ b/client/src/app/metamodel/reducers/column.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Column } from '../models';
 import * as columnActions from '../actions/column.actions';
 
+/**
+ * Interface for column state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Column> {
     columnListIsLoading: boolean;
     columnListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const columnReducer = createReducer(
     }),
     on(columnActions.loadColumnListSuccess, (state, { columns }) => {
         return adapter.setAll(
-            columns, 
-            { 
+            columns,
+            {
                 ...state,
                 columnListIsLoading: false,
                 columnListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/criteria-family.reducer.spec.ts b/client/src/app/metamodel/reducers/criteria-family.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..249bd1c78ba8250d081513fd2b93b6c26de65772
--- /dev/null
+++ b/client/src/app/metamodel/reducers/criteria-family.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromCriteriaFamily from './criteria-family.reducer';
+import * as criteriaFamilyActions from '../actions/criteria-family.actions';
+import { CRITERIA_FAMILY, CRITERIA_FAMILY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] CriteriaFamily reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromCriteriaFamily;
+        const action = { type: 'Unknown' };
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadCriteriaFamilyList action should set criteriaFamilyListIsLoading to true', () => {
+        const { initialState } = fromCriteriaFamily;
+        const action = criteriaFamilyActions.loadCriteriaFamilyList();
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.criteriaFamilyListIsLoading).toEqual(true);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadCriteriaFamilyListSuccess action should add criteriaFamily list, set criteriaFamilyListIsLoading to false and set criteriaFamilyListIsLoaded to true', () => {
+        const { initialState } = fromCriteriaFamily;
+        const action = criteriaFamilyActions.loadCriteriaFamilyListSuccess({ criteriaFamilies: CRITERIA_FAMILY_LIST });
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.criteriaFamilyListIsLoading).toEqual(false);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadCriteriaFamilyListFail action should set criteriaFamilyListIsLoading to false', () => {
+        const { initialState } = fromCriteriaFamily;
+        const action = criteriaFamilyActions.loadCriteriaFamilyListFail();
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.criteriaFamilyListIsLoading).toEqual(false);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addCriteriaFamilySuccess action should add a criteriaFamily', () => {
+        const { initialState } = fromCriteriaFamily;
+        const action = criteriaFamilyActions.addCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.criteriaFamilyListIsLoading).toEqual(false);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editCriteriaFamilySuccess action should modify a criteriaFamily', () => {
+        const initialState = {
+            ...fromCriteriaFamily.initialState,
+            ids: [1],
+            entities: { 1: { ...CRITERIA_FAMILY, label: 'label' }}
+        };
+        const action = criteriaFamilyActions.editCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(CRITERIA_FAMILY);
+        expect(state.criteriaFamilyListIsLoading).toEqual(false);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteCriteriaFamilySuccess action should modify a criteriaFamily', () => {
+        const initialState = {
+            ...fromCriteriaFamily.initialState,
+            ids: [1],
+            entities: { 1: CRITERIA_FAMILY }
+        };
+        const action = criteriaFamilyActions.deleteCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY });
+        const state = fromCriteriaFamily.criteriaFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.criteriaFamilyListIsLoading).toEqual(false);
+        expect(state.criteriaFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get criteriaFamilyListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromCriteriaFamily.criteriaFamilyReducer(undefined, action);
+
+        expect(fromCriteriaFamily.selectCriteriaFamilyListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get criteriaFamilyListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromCriteriaFamily.criteriaFamilyReducer(undefined, action);
+
+        expect(fromCriteriaFamily.selectCriteriaFamilyListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/criteria-family.reducer.ts b/client/src/app/metamodel/reducers/criteria-family.reducer.ts
index 76ccaae48cbaa1e8d6b28da6f878b495756734d6..9e072db237fd32c014340643282e4194b8f7c9fd 100644
--- a/client/src/app/metamodel/reducers/criteria-family.reducer.ts
+++ b/client/src/app/metamodel/reducers/criteria-family.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { CriteriaFamily } from '../models';
 import * as criteriaFamilyActions from '../actions/criteria-family.actions';
 
+/**
+ * Interface for criteria family state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<CriteriaFamily> {
     criteriaFamilyListIsLoading: boolean;
     criteriaFamilyListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const criteriaFamilyReducer = createReducer(
     }),
     on(criteriaFamilyActions.loadCriteriaFamilyListSuccess, (state, { criteriaFamilies }) => {
         return adapter.setAll(
-            criteriaFamilies, 
-            { 
+            criteriaFamilies,
+            {
                 ...state,
                 criteriaFamilyListIsLoading: false,
                 criteriaFamilyListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/database.reducer.spec.ts b/client/src/app/metamodel/reducers/database.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd92d42f2ec01f3d5c4a5e498cb6cd9ecf70181d
--- /dev/null
+++ b/client/src/app/metamodel/reducers/database.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromDatabase from './database.reducer';
+import * as databaseActions from '../actions/database.actions';
+import { DATABASE, DATABASE_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Database reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromDatabase;
+        const action = { type: 'Unknown' };
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadDatabaseList action should set databaseListIsLoading to true', () => {
+        const { initialState } = fromDatabase;
+        const action = databaseActions.loadDatabaseList();
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.databaseListIsLoading).toEqual(true);
+        expect(state.databaseListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatabaseListSuccess action should add database list, set databaseListIsLoading to false and set databaseListIsLoaded to true', () => {
+        const { initialState } = fromDatabase;
+        const action = databaseActions.loadDatabaseListSuccess({ databases: DATABASE_LIST });
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.databaseListIsLoading).toEqual(false);
+        expect(state.databaseListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatabaseListFail action should set databaseListIsLoading to false', () => {
+        const { initialState } = fromDatabase;
+        const action = databaseActions.loadDatabaseListFail();
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.databaseListIsLoading).toEqual(false);
+        expect(state.databaseListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addDatabaseSuccess action should add a database', () => {
+        const { initialState } = fromDatabase;
+        const action = databaseActions.addDatabaseSuccess({ database: DATABASE });
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.databaseListIsLoading).toEqual(false);
+        expect(state.databaseListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editDatabaseSuccess action should modify a database', () => {
+        const initialState = {
+            ...fromDatabase.initialState,
+            ids: [1],
+            entities: { 1: { ...DATABASE, label: 'label' }}
+        };
+        const action = databaseActions.editDatabaseSuccess({ database: DATABASE });
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(DATABASE);
+        expect(state.databaseListIsLoading).toEqual(false);
+        expect(state.databaseListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteDatabaseSuccess action should modify a database', () => {
+        const initialState = {
+            ...fromDatabase.initialState,
+            ids: [1],
+            entities: { 1: DATABASE }
+        };
+        const action = databaseActions.deleteDatabaseSuccess({ database: DATABASE });
+        const state = fromDatabase.databaseReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.databaseListIsLoading).toEqual(false);
+        expect(state.databaseListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get databaseListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromDatabase.databaseReducer(undefined, action);
+
+        expect(fromDatabase.selectDatabaseListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get databaseListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromDatabase.databaseReducer(undefined, action);
+
+        expect(fromDatabase.selectDatabaseListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/database.reducer.ts b/client/src/app/metamodel/reducers/database.reducer.ts
index 045397b12d64d1e7d5c768783fe7e033064e60e6..d361230babec00ec9b86a676e39156de9f018a70 100644
--- a/client/src/app/metamodel/reducers/database.reducer.ts
+++ b/client/src/app/metamodel/reducers/database.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Database } from '../models';
 import * as databaseActions from '../actions/database.actions';
 
+/**
+ * Interface for database state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Database> {
     databaseListIsLoading: boolean;
     databaseListIsLoaded: boolean;
@@ -35,8 +40,8 @@ export const databaseReducer = createReducer(
     }),
     on(databaseActions.loadDatabaseListSuccess, (state, { databases }) => {
         return adapter.setAll(
-            databases, 
-            { 
+            databases,
+            {
                 ...state,
                 databaseListIsLoading: false,
                 databaseListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/dataset-family.reducer.spec.ts b/client/src/app/metamodel/reducers/dataset-family.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0175fd1b444cd9f18996a1ae53ac0867be3fa94
--- /dev/null
+++ b/client/src/app/metamodel/reducers/dataset-family.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromDatasetFamily from './dataset-family.reducer';
+import * as datasetFamilyActions from '../actions/dataset-family.actions';
+import { DATASET_FAMILY, DATASET_FAMILY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] DatasetFamily reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromDatasetFamily;
+        const action = { type: 'Unknown' };
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadDatasetFamilyList action should set datasetFamilyListIsLoading to true', () => {
+        const { initialState } = fromDatasetFamily;
+        const action = datasetFamilyActions.loadDatasetFamilyList();
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.datasetFamilyListIsLoading).toEqual(true);
+        expect(state.datasetFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatasetFamilyListSuccess action should add datasetFamily list, set datasetFamilyListIsLoading to false and set datasetFamilyListIsLoaded to true', () => {
+        const { initialState } = fromDatasetFamily;
+        const action = datasetFamilyActions.loadDatasetFamilyListSuccess({ datasetFamilies: DATASET_FAMILY_LIST });
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.datasetFamilyListIsLoading).toEqual(false);
+        expect(state.datasetFamilyListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatasetFamilyListFail action should set datasetFamilyListIsLoading to false', () => {
+        const { initialState } = fromDatasetFamily;
+        const action = datasetFamilyActions.loadDatasetFamilyListFail();
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.datasetFamilyListIsLoading).toEqual(false);
+        expect(state.datasetFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addDatasetFamilySuccess action should add a datasetFamily', () => {
+        const { initialState } = fromDatasetFamily;
+        const action = datasetFamilyActions.addDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.datasetFamilyListIsLoading).toEqual(false);
+        expect(state.datasetFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editDatasetFamilySuccess action should modify a datasetFamily', () => {
+        const initialState = {
+            ...fromDatasetFamily.initialState,
+            ids: [1],
+            entities: { 1: { ...DATASET_FAMILY, label: 'label' }}
+        };
+        const action = datasetFamilyActions.editDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(DATASET_FAMILY);
+        expect(state.datasetFamilyListIsLoading).toEqual(false);
+        expect(state.datasetFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteDatasetFamilySuccess action should modify a datasetFamily', () => {
+        const initialState = {
+            ...fromDatasetFamily.initialState,
+            ids: [1],
+            entities: { 1: DATASET_FAMILY }
+        };
+        const action = datasetFamilyActions.deleteDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY });
+        const state = fromDatasetFamily.datasetFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.datasetFamilyListIsLoading).toEqual(false);
+        expect(state.datasetFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get datasetFamilyListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromDatasetFamily.datasetFamilyReducer(undefined, action);
+
+        expect(fromDatasetFamily.selectDatasetFamilyListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get datasetFamilyListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromDatasetFamily.datasetFamilyReducer(undefined, action);
+
+        expect(fromDatasetFamily.selectDatasetFamilyListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/dataset-family.reducer.ts b/client/src/app/metamodel/reducers/dataset-family.reducer.ts
index 923f88374e7b4211d4dd156554d7fa239bce8aa8..df8f9452ab6e0de8e384a7ab8724dde030b402f5 100644
--- a/client/src/app/metamodel/reducers/dataset-family.reducer.ts
+++ b/client/src/app/metamodel/reducers/dataset-family.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { DatasetFamily } from '../models';
 import * as datasetFamilyActions from '../actions/dataset-family.actions';
 
+/**
+ * Interface for dataset family state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<DatasetFamily> {
     datasetFamilyListIsLoading: boolean;
     datasetFamilyListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const datasetFamilyReducer = createReducer(
     }),
     on(datasetFamilyActions.loadDatasetFamilyListSuccess, (state, { datasetFamilies }) => {
         return adapter.setAll(
-            datasetFamilies, 
-            { 
+            datasetFamilies,
+            {
                 ...state,
                 datasetFamilyListIsLoading: false,
                 datasetFamilyListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/dataset.reducer.spec.ts b/client/src/app/metamodel/reducers/dataset.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9284176096f040ebaac4ecb99f735029de90c820
--- /dev/null
+++ b/client/src/app/metamodel/reducers/dataset.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromDataset from './dataset.reducer';
+import * as datasetActions from '../actions/dataset.actions';
+import { DATASET, DATASET_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Dataset reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromDataset;
+        const action = { type: 'Unknown' };
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadDatasetList action should set datasetListIsLoading to true', () => {
+        const { initialState } = fromDataset;
+        const action = datasetActions.loadDatasetList();
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.datasetListIsLoading).toEqual(true);
+        expect(state.datasetListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatasetListSuccess action should add dataset list, set datasetListIsLoading to false and set datasetListIsLoaded to true', () => {
+        const { initialState } = fromDataset;
+        const action = datasetActions.loadDatasetListSuccess({ datasets: DATASET_LIST });
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('myDataset');
+        expect(state.ids).toContain('anotherDataset');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.datasetListIsLoading).toEqual(false);
+        expect(state.datasetListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadDatasetListFail action should set datasetListIsLoading to false', () => {
+        const { initialState } = fromDataset;
+        const action = datasetActions.loadDatasetListFail();
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.datasetListIsLoading).toEqual(false);
+        expect(state.datasetListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addDatasetSuccess action should add a dataset', () => {
+        const { initialState } = fromDataset;
+        const action = datasetActions.addDatasetSuccess({ dataset: DATASET });
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('myDataset');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.datasetListIsLoading).toEqual(false);
+        expect(state.datasetListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editDatasetSuccess action should modify a dataset', () => {
+        const initialState = {
+            ...fromDataset.initialState,
+            ids: ['myDataset'],
+            entities: { 'myDataset': { ...DATASET, label: 'label' }}
+        };
+        const action = datasetActions.editDatasetSuccess({ dataset: DATASET });
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('myDataset');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities['myDataset']).toEqual(DATASET);
+        expect(state.datasetListIsLoading).toEqual(false);
+        expect(state.datasetListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteDatasetSuccess action should modify a dataset', () => {
+        const initialState = {
+            ...fromDataset.initialState,
+            ids: ['myDataset'],
+            entities: { 'myDataset': DATASET }
+        };
+        const action = datasetActions.deleteDatasetSuccess({ dataset: DATASET });
+        const state = fromDataset.datasetReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.datasetListIsLoading).toEqual(false);
+        expect(state.datasetListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get datasetListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromDataset.datasetReducer(undefined, action);
+
+        expect(fromDataset.selectDatasetListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get datasetListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromDataset.datasetReducer(undefined, action);
+
+        expect(fromDataset.selectDatasetListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/dataset.reducer.ts b/client/src/app/metamodel/reducers/dataset.reducer.ts
index becb9691e69c08353c027083fad1b096dcc8f522..013feea1abd912d6a2a9aac6c9fe3c4dbe78c450 100644
--- a/client/src/app/metamodel/reducers/dataset.reducer.ts
+++ b/client/src/app/metamodel/reducers/dataset.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Dataset } from '../models';
 import * as datasetActions from '../actions/dataset.actions';
 
+/**
+ * Interface for dataset state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Dataset> {
     datasetListIsLoading: boolean;
     datasetListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const datasetReducer = createReducer(
     }),
     on(datasetActions.loadDatasetListSuccess, (state, { datasets }) => {
         return adapter.setAll(
-            datasets, 
-            { 
+            datasets,
+            {
                 ...state,
                 datasetListIsLoading: false,
                 datasetListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/group.reducer.spec.ts b/client/src/app/metamodel/reducers/group.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..354425566c71e03b08fbf0c9b65491842526ef4a
--- /dev/null
+++ b/client/src/app/metamodel/reducers/group.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromGroup from './group.reducer';
+import * as groupActions from '../actions/group.actions';
+import { GROUP, GROUP_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Group reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromGroup;
+        const action = { type: 'Unknown' };
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadGroupList action should set groupListIsLoading to true', () => {
+        const { initialState } = fromGroup;
+        const action = groupActions.loadGroupList();
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.groupListIsLoading).toEqual(true);
+        expect(state.groupListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadGroupListSuccess action should add group list, set groupListIsLoading to false and set groupListIsLoaded to true', () => {
+        const { initialState } = fromGroup;
+        const action = groupActions.loadGroupListSuccess({ groups: GROUP_LIST });
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.groupListIsLoading).toEqual(false);
+        expect(state.groupListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadGroupListFail action should set groupListIsLoading to false', () => {
+        const { initialState } = fromGroup;
+        const action = groupActions.loadGroupListFail();
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.groupListIsLoading).toEqual(false);
+        expect(state.groupListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addGroupSuccess action should add a group', () => {
+        const { initialState } = fromGroup;
+        const action = groupActions.addGroupSuccess({ group: GROUP });
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.groupListIsLoading).toEqual(false);
+        expect(state.groupListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editGroupSuccess action should modify a group', () => {
+        const initialState = {
+            ...fromGroup.initialState,
+            ids: [1],
+            entities: { 1: { ...GROUP, role: 'role' }}
+        };
+        const action = groupActions.editGroupSuccess({ group: GROUP });
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(GROUP);
+        expect(state.groupListIsLoading).toEqual(false);
+        expect(state.groupListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteGroupSuccess action should modify a group', () => {
+        const initialState = {
+            ...fromGroup.initialState,
+            ids: [1],
+            entities: { 1: GROUP }
+        };
+        const action = groupActions.deleteGroupSuccess({ group: GROUP });
+        const state = fromGroup.groupReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.groupListIsLoading).toEqual(false);
+        expect(state.groupListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get groupListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromGroup.groupReducer(undefined, action);
+
+        expect(fromGroup.selectGroupListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get groupListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromGroup.groupReducer(undefined, action);
+
+        expect(fromGroup.selectGroupListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/group.reducer.ts b/client/src/app/metamodel/reducers/group.reducer.ts
index ad3c3f4343c53db72b4880b56662315308c6af97..3037765737375444edf3397c598de886dec72b4c 100644
--- a/client/src/app/metamodel/reducers/group.reducer.ts
+++ b/client/src/app/metamodel/reducers/group.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Group } from '../models';
 import * as groupActions from '../actions/group.actions';
 
+/**
+ * Interface for group state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Group> {
     groupListIsLoading: boolean;
     groupListIsLoaded: boolean;
@@ -35,8 +40,8 @@ export const groupReducer = createReducer(
     }),
     on(groupActions.loadGroupListSuccess, (state, { groups }) => {
         return adapter.setAll(
-            groups, 
-            { 
+            groups,
+            {
                 ...state,
                 groupListIsLoading: false,
                 groupListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/instance.reducer.spec.ts b/client/src/app/metamodel/reducers/instance.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e13bdc69f4257b10f48bd884136dd75991e6bb9
--- /dev/null
+++ b/client/src/app/metamodel/reducers/instance.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromInstance from './instance.reducer';
+import * as instanceActions from '../actions/instance.actions';
+import { INSTANCE, INSTANCE_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Instance reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromInstance;
+        const action = { type: 'Unknown' };
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadInstanceList action should set instanceListIsLoading to true', () => {
+        const { initialState } = fromInstance;
+        const action = instanceActions.loadInstanceList();
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.instanceListIsLoading).toEqual(true);
+        expect(state.instanceListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadInstanceListSuccess action should add instance list, set instanceListIsLoading to false and set instanceListIsLoaded to true', () => {
+        const { initialState } = fromInstance;
+        const action = instanceActions.loadInstanceListSuccess({ instances: INSTANCE_LIST });
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('myInstance');
+        expect(state.ids).toContain('myOtherInstance');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.instanceListIsLoading).toEqual(false);
+        expect(state.instanceListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadInstanceListFail action should set instanceListIsLoading to false', () => {
+        const { initialState } = fromInstance;
+        const action = instanceActions.loadInstanceListFail();
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.instanceListIsLoading).toEqual(false);
+        expect(state.instanceListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addInstanceSuccess action should add a instance', () => {
+        const { initialState } = fromInstance;
+        const action = instanceActions.addInstanceSuccess({ instance: INSTANCE });
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('myInstance');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.instanceListIsLoading).toEqual(false);
+        expect(state.instanceListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editInstanceSuccess action should modify a instance', () => {
+        const initialState = {
+            ...fromInstance.initialState,
+            ids: ['myInstance'],
+            entities: { 'myInstance': { ...INSTANCE, label: 'label' }}
+        };
+        const action = instanceActions.editInstanceSuccess({ instance: INSTANCE });
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('myInstance');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities['myInstance']).toEqual(INSTANCE);
+        expect(state.instanceListIsLoading).toEqual(false);
+        expect(state.instanceListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteInstanceSuccess action should modify a instance', () => {
+        const initialState = {
+            ...fromInstance.initialState,
+            ids: ['myInstance'],
+            entities: { 'myInstance': INSTANCE }
+        };
+        const action = instanceActions.deleteInstanceSuccess({ instance: INSTANCE });
+        const state = fromInstance.instanceReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.instanceListIsLoading).toEqual(false);
+        expect(state.instanceListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get instanceListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromInstance.instanceReducer(undefined, action);
+
+        expect(fromInstance.selectInstanceListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get instanceListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromInstance.instanceReducer(undefined, action);
+
+        expect(fromInstance.selectInstanceListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/instance.reducer.ts b/client/src/app/metamodel/reducers/instance.reducer.ts
index 33a282126917158df332a246bae615276f47dc17..e9f454ef2dae9be6d92023c64e4a30ef7ac6baf0 100644
--- a/client/src/app/metamodel/reducers/instance.reducer.ts
+++ b/client/src/app/metamodel/reducers/instance.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Instance } from '../models';
 import * as instanceActions from '../actions/instance.actions';
 
+/**
+ * Interface for instance state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Instance> {
     instanceListIsLoading: boolean;
     instanceListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const instanceReducer = createReducer(
     }),
     on(instanceActions.loadInstanceListSuccess, (state, { instances }) => {
         return adapter.setAll(
-            instances, 
-            { 
+            instances,
+            {
                 ...state,
                 instanceListIsLoading: false,
                 instanceListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/output-category.reducer.spec.ts b/client/src/app/metamodel/reducers/output-category.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5af1ebc777ffbcf3b7278efd7793b2370e7d8262
--- /dev/null
+++ b/client/src/app/metamodel/reducers/output-category.reducer.spec.ts
@@ -0,0 +1,108 @@
+import { Action } from '@ngrx/store';
+
+import * as fromOutputCategory from './output-category.reducer';
+import * as outputCategoryActions from '../actions/output-category.actions';
+import { CATEGORY, CATEGORY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] OutputCategory reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromOutputCategory;
+        const action = { type: 'Unknown' };
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadOutputCategoryList action should set outputCategoryListIsLoading to true', () => {
+        const { initialState } = fromOutputCategory;
+        const action = outputCategoryActions.loadOutputCategoryList();
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.outputCategoryListIsLoading).toEqual(true);
+        expect(state.outputCategoryListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadOutputCategoryListSuccess action should add outputCategory list, set outputCategoryListIsLoading to false and set outputCategoryListIsLoaded to true', () => {
+        const { initialState } = fromOutputCategory;
+        const action = outputCategoryActions.loadOutputCategoryListSuccess({ outputCategories: CATEGORY_LIST });
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(3);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(state.ids).toContain(3);
+        expect(Object.keys(state.entities).length).toEqual(3);
+        expect(state.outputCategoryListIsLoading).toEqual(false);
+        expect(state.outputCategoryListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadOutputCategoryListFail action should set outputCategoryListIsLoading to false', () => {
+        const { initialState } = fromOutputCategory;
+        const action = outputCategoryActions.loadOutputCategoryListFail();
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.outputCategoryListIsLoading).toEqual(false);
+        expect(state.outputCategoryListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addOutputCategorySuccess action should add a outputCategory', () => {
+        const { initialState } = fromOutputCategory;
+        const action = outputCategoryActions.addOutputCategorySuccess({ outputCategory: CATEGORY });
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.outputCategoryListIsLoading).toEqual(false);
+        expect(state.outputCategoryListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editOutputCategorySuccess action should modify a outputCategory', () => {
+        const initialState = {
+            ...fromOutputCategory.initialState,
+            ids: [1],
+            entities: { 1: { ...CATEGORY, label: 'label' }}
+        };
+        const action = outputCategoryActions.editOutputCategorySuccess({ outputCategory: CATEGORY });
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(CATEGORY);
+        expect(state.outputCategoryListIsLoading).toEqual(false);
+        expect(state.outputCategoryListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteOutputCategorySuccess action should modify a outputCategory', () => {
+        const initialState = {
+            ...fromOutputCategory.initialState,
+            ids: [1],
+            entities: { 1: CATEGORY }
+        };
+        const action = outputCategoryActions.deleteOutputCategorySuccess({ outputCategory: CATEGORY });
+        const state = fromOutputCategory.outputCategoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.outputCategoryListIsLoading).toEqual(false);
+        expect(state.outputCategoryListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get outputCategoryListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromOutputCategory.outputCategoryReducer(undefined, action);
+
+        expect(fromOutputCategory.selectOutputCategoryListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get outputCategoryListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromOutputCategory.outputCategoryReducer(undefined, action);
+
+        expect(fromOutputCategory.selectOutputCategoryListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/output-category.reducer.ts b/client/src/app/metamodel/reducers/output-category.reducer.ts
index ddbed47415434fee7336a5e45078c8557d1b956e..437058cbeca194d5dcbf83789d8913ba0417ecfe 100644
--- a/client/src/app/metamodel/reducers/output-category.reducer.ts
+++ b/client/src/app/metamodel/reducers/output-category.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { OutputCategory } from '../models';
 import * as outputCategoryActions from '../actions/output-category.actions';
 
+/**
+ * Interface for output category state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<OutputCategory> {
     outputCategoryListIsLoading: boolean;
     outputCategoryListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const outputCategoryReducer = createReducer(
     }),
     on(outputCategoryActions.loadOutputCategoryListSuccess, (state, { outputCategories }) => {
         return adapter.setAll(
-            outputCategories, 
-            { 
+            outputCategories,
+            {
                 ...state,
                 outputCategoryListIsLoading: false,
                 outputCategoryListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/output-family.reducer.spec.ts b/client/src/app/metamodel/reducers/output-family.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e2397d9aa30d2a6b848b4104ba339e906ca0c39
--- /dev/null
+++ b/client/src/app/metamodel/reducers/output-family.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromOutputFamily from './output-family.reducer';
+import * as outputFamilyActions from '../actions/output-family.actions';
+import { OUTPUT_FAMILY, OUTPUT_FAMILY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] OutputFamily reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromOutputFamily;
+        const action = { type: 'Unknown' };
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadOutputFamilyList action should set outputFamilyListIsLoading to true', () => {
+        const { initialState } = fromOutputFamily;
+        const action = outputFamilyActions.loadOutputFamilyList();
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.outputFamilyListIsLoading).toEqual(true);
+        expect(state.outputFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadOutputFamilyListSuccess action should add outputFamily list, set outputFamilyListIsLoading to false and set outputFamilyListIsLoaded to true', () => {
+        const { initialState } = fromOutputFamily;
+        const action = outputFamilyActions.loadOutputFamilyListSuccess({ outputFamilies: OUTPUT_FAMILY_LIST });
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.outputFamilyListIsLoading).toEqual(false);
+        expect(state.outputFamilyListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadOutputFamilyListFail action should set outputFamilyListIsLoading to false', () => {
+        const { initialState } = fromOutputFamily;
+        const action = outputFamilyActions.loadOutputFamilyListFail();
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.outputFamilyListIsLoading).toEqual(false);
+        expect(state.outputFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addOutputFamilySuccess action should add a outputFamily', () => {
+        const { initialState } = fromOutputFamily;
+        const action = outputFamilyActions.addOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.outputFamilyListIsLoading).toEqual(false);
+        expect(state.outputFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editOutputFamilySuccess action should modify a outputFamily', () => {
+        const initialState = {
+            ...fromOutputFamily.initialState,
+            ids: [1],
+            entities: { 1: { ...OUTPUT_FAMILY, label: 'label' }}
+        };
+        const action = outputFamilyActions.editOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(OUTPUT_FAMILY);
+        expect(state.outputFamilyListIsLoading).toEqual(false);
+        expect(state.outputFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteOutputFamilySuccess action should modify a outputFamily', () => {
+        const initialState = {
+            ...fromOutputFamily.initialState,
+            ids: [1],
+            entities: { 1: OUTPUT_FAMILY }
+        };
+        const action = outputFamilyActions.deleteOutputFamilySuccess({ outputFamily: OUTPUT_FAMILY });
+        const state = fromOutputFamily.outputFamilyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.outputFamilyListIsLoading).toEqual(false);
+        expect(state.outputFamilyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get outputFamilyListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromOutputFamily.outputFamilyReducer(undefined, action);
+
+        expect(fromOutputFamily.selectOutputFamilyListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get outputFamilyListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromOutputFamily.outputFamilyReducer(undefined, action);
+
+        expect(fromOutputFamily.selectOutputFamilyListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/output-family.reducer.ts b/client/src/app/metamodel/reducers/output-family.reducer.ts
index cb7c5f6b38320c69a2542149313eaae47467d41a..93fffd5cad3a09c6a52bced1a3885c59ab7f8245 100644
--- a/client/src/app/metamodel/reducers/output-family.reducer.ts
+++ b/client/src/app/metamodel/reducers/output-family.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { OutputFamily } from '../models';
 import * as outputFamilyActions from '../actions/output-family.actions';
 
+/**
+ * Interface for output family state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<OutputFamily> {
     outputFamilyListIsLoading: boolean;
     outputFamilyListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const outputFamilyReducer = createReducer(
     }),
     on(outputFamilyActions.loadOutputFamilyListSuccess, (state, { outputFamilies }) => {
         return adapter.setAll(
-            outputFamilies, 
-            { 
+            outputFamilies,
+            {
                 ...state,
                 outputFamilyListIsLoading: false,
                 outputFamilyListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts b/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..942dcd439222fccc6ad85e3725e9cebe70b442c4
--- /dev/null
+++ b/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts
@@ -0,0 +1,63 @@
+import { Action } from '@ngrx/store';
+
+import * as fromRootDirectory from './root-directory.reducer';
+import * as rootDirectoryActions from '../actions/root-directory.actions';
+import { FILES } from '../../../test-data';
+
+describe('[Metamodel][Reducers] RootDirectory reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromRootDirectory;
+        const action = { type: 'Unknown' };
+        const state = fromRootDirectory.rootDirectoryReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadRootDirectory action should set rootDirectoryIsLoading to true', () => {
+        const { initialState } = fromRootDirectory;
+        const action = rootDirectoryActions.loadRootDirectory({ path: 'path' });
+        const state = fromRootDirectory.rootDirectoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.rootDirectoryIsLoading).toEqual(true);
+        expect(state.rootDirectoryIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadRootDirectorySuccess action should add files, set rootDirectoryIsLoading to false and set rootDirectoryIsLoaded to true', () => {
+        const { initialState } = fromRootDirectory;
+        const action = rootDirectoryActions.loadRootDirectorySuccess({ files: FILES });
+        const state = fromRootDirectory.rootDirectoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('file-one');
+        expect(state.ids).toContain('file-two');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.rootDirectoryIsLoading).toEqual(false);
+        expect(state.rootDirectoryIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadRootDirectoryFail action should set rootDirectoryIsLoading to false', () => {
+        const { initialState } = fromRootDirectory;
+        const action = rootDirectoryActions.loadRootDirectoryFail();
+        const state = fromRootDirectory.rootDirectoryReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.rootDirectoryIsLoading).toEqual(false);
+        expect(state.rootDirectoryIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get rootDirectoryIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromRootDirectory.rootDirectoryReducer(undefined, action);
+
+        expect(fromRootDirectory.selectRootDirectoryIsLoading(state)).toEqual(false);
+    });
+
+    it('should get rootDirectoryIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromRootDirectory.rootDirectoryReducer(undefined, action);
+
+        expect(fromRootDirectory.selectRootDirectoryIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/root-directory.reducer.ts b/client/src/app/metamodel/reducers/root-directory.reducer.ts
index d8d7082f7943a134b33a7c0f64a4cd2ba01b9f3e..efab0b9d840fc4bb35267e4a32b820987b221bea 100644
--- a/client/src/app/metamodel/reducers/root-directory.reducer.ts
+++ b/client/src/app/metamodel/reducers/root-directory.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import * as rootDirectoryActions from '../actions/root-directory.actions';
 import { FileInfo } from '../models';
 
+/**
+ * Interface for root directory state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<FileInfo> {
     rootDirectoryIsLoading: boolean;
     rootDirectoryIsLoaded: boolean;
@@ -39,8 +44,8 @@ export const rootDirectoryReducer = createReducer(
     }),
     on(rootDirectoryActions.loadRootDirectorySuccess, (state, { files }) => {
         return adapter.setAll(
-            files, 
-            { 
+            files,
+            {
                 ...state,
                 rootDirectoryIsLoading: false,
                 rootDirectoryIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/select-option.reducer.spec.ts b/client/src/app/metamodel/reducers/select-option.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..000a74966ba13f3292949a801719dc165431b6b8
--- /dev/null
+++ b/client/src/app/metamodel/reducers/select-option.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromSelectOption from './select-option.reducer';
+import * as selectOptionActions from '../actions/select-option.actions';
+import { SELECT_OPTION, SELECT_OPTION_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] SelectOption reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromSelectOption;
+        const action = { type: 'Unknown' };
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadSelectOptionList action should set selectOptionListIsLoading to true', () => {
+        const { initialState } = fromSelectOption;
+        const action = selectOptionActions.loadSelectOptionList();
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.selectOptionListIsLoading).toEqual(true);
+        expect(state.selectOptionListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSelectOptionListSuccess action should add selectOption list, set selectOptionListIsLoading to false and set selectOptionListIsLoaded to true', () => {
+        const { initialState } = fromSelectOption;
+        const action = selectOptionActions.loadSelectOptionListSuccess({ selectOptions: SELECT_OPTION_LIST });
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain(1);
+        expect(state.ids).toContain(2);
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.selectOptionListIsLoading).toEqual(false);
+        expect(state.selectOptionListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSelectOptionListFail action should set selectOptionListIsLoading to false', () => {
+        const { initialState } = fromSelectOption;
+        const action = selectOptionActions.loadSelectOptionListFail();
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.selectOptionListIsLoading).toEqual(false);
+        expect(state.selectOptionListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addSelectOptionSuccess action should add a selectOption', () => {
+        const { initialState } = fromSelectOption;
+        const action = selectOptionActions.addSelectOptionSuccess({ selectOption: SELECT_OPTION });
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.selectOptionListIsLoading).toEqual(false);
+        expect(state.selectOptionListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editSelectOptionSuccess action should modify a selectOption', () => {
+        const initialState = {
+            ...fromSelectOption.initialState,
+            ids: [1],
+            entities: { 1: { ...SELECT_OPTION, label: 'label' }}
+        };
+        const action = selectOptionActions.editSelectOptionSuccess({ selectOption: SELECT_OPTION });
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain(1);
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities[1]).toEqual(SELECT_OPTION);
+        expect(state.selectOptionListIsLoading).toEqual(false);
+        expect(state.selectOptionListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteSelectOptionSuccess action should modify a selectOption', () => {
+        const initialState = {
+            ...fromSelectOption.initialState,
+            ids: [1],
+            entities: { 1: SELECT_OPTION }
+        };
+        const action = selectOptionActions.deleteSelectOptionSuccess({ selectOption: SELECT_OPTION });
+        const state = fromSelectOption.selectOptionReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.selectOptionListIsLoading).toEqual(false);
+        expect(state.selectOptionListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get selectOptionListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromSelectOption.selectOptionReducer(undefined, action);
+
+        expect(fromSelectOption.selectSelectOptionListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get selectOptionListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromSelectOption.selectOptionReducer(undefined, action);
+
+        expect(fromSelectOption.selectSelectOptionListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/select-option.reducer.ts b/client/src/app/metamodel/reducers/select-option.reducer.ts
index f63ffbd5c6f5c2cdc84386301784731bd29fb681..f6468561d435d3f003482acd019e685171be1f65 100644
--- a/client/src/app/metamodel/reducers/select-option.reducer.ts
+++ b/client/src/app/metamodel/reducers/select-option.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { SelectOption } from '../models';
 import * as selectOptionActions from '../actions/select-option.actions';
 
+/**
+ * Interface for select option state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<SelectOption> {
     selectOptionListIsLoading: boolean;
     selectOptionListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const selectOptionReducer = createReducer(
     }),
     on(selectOptionActions.loadSelectOptionListSuccess, (state, { selectOptions }) => {
         return adapter.setAll(
-            selectOptions, 
-            { 
+            selectOptions,
+            {
                 ...state,
                 selectOptionListIsLoading: false,
                 selectOptionListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/select.reducer.spec.ts b/client/src/app/metamodel/reducers/select.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e21a6f55ffd4e830d4a118dd2ec666340400457
--- /dev/null
+++ b/client/src/app/metamodel/reducers/select.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromSelect from './select.reducer';
+import * as selectActions from '../actions/select.actions';
+import { SELECT, SELECT_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Select reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromSelect;
+        const action = { type: 'Unknown' };
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadSelectList action should set selectListIsLoading to true', () => {
+        const { initialState } = fromSelect;
+        const action = selectActions.loadSelectList();
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.selectListIsLoading).toEqual(true);
+        expect(state.selectListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSelectListSuccess action should add select list, set selectListIsLoading to false and set selectListIsLoaded to true', () => {
+        const { initialState } = fromSelect;
+        const action = selectActions.loadSelectListSuccess({ selects: SELECT_LIST });
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('select-one');
+        expect(state.ids).toContain('select-two');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.selectListIsLoading).toEqual(false);
+        expect(state.selectListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSelectListFail action should set selectListIsLoading to false', () => {
+        const { initialState } = fromSelect;
+        const action = selectActions.loadSelectListFail();
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.selectListIsLoading).toEqual(false);
+        expect(state.selectListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addSelectSuccess action should add a select', () => {
+        const { initialState } = fromSelect;
+        const action = selectActions.addSelectSuccess({ select: SELECT });
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('mySelect');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.selectListIsLoading).toEqual(false);
+        expect(state.selectListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editSelectSuccess action should modify a select', () => {
+        const initialState = {
+            ...fromSelect.initialState,
+            ids: ['mySelect'],
+            entities: { 'mySelect': { ...SELECT, label: 'label' }}
+        };
+        const action = selectActions.editSelectSuccess({ select: SELECT });
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('mySelect');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities['mySelect']).toEqual(SELECT);
+        expect(state.selectListIsLoading).toEqual(false);
+        expect(state.selectListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteSelectSuccess action should modify a select', () => {
+        const initialState = {
+            ...fromSelect.initialState,
+            ids: ['mySelect'],
+            entities: { 'mySelect': SELECT }
+        };
+        const action = selectActions.deleteSelectSuccess({ select: SELECT });
+        const state = fromSelect.selectReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.selectListIsLoading).toEqual(false);
+        expect(state.selectListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get selectListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromSelect.selectReducer(undefined, action);
+
+        expect(fromSelect.selectSelectListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get selectListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromSelect.selectReducer(undefined, action);
+
+        expect(fromSelect.selectSelectListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/select.reducer.ts b/client/src/app/metamodel/reducers/select.reducer.ts
index f12df3a564e146151601885be5cc0e117d8641c6..942f69e83a343c42f3d9bd1538f88921e447db47 100644
--- a/client/src/app/metamodel/reducers/select.reducer.ts
+++ b/client/src/app/metamodel/reducers/select.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Select } from '../models';
 import * as selectActions from '../actions/select.actions';
 
+/**
+ * Interface for select state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Select> {
     selectListIsLoading: boolean;
     selectListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const selectReducer = createReducer(
     }),
     on(selectActions.loadSelectListSuccess, (state, { selects }) => {
         return adapter.setAll(
-            selects, 
-            { 
+            selects,
+            {
                 ...state,
                 selectListIsLoading: false,
                 selectListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/survey.reducer.spec.ts b/client/src/app/metamodel/reducers/survey.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e54b0f7716c60d2736bdfaf3d68e4043832fd9a
--- /dev/null
+++ b/client/src/app/metamodel/reducers/survey.reducer.spec.ts
@@ -0,0 +1,107 @@
+import { Action } from '@ngrx/store';
+
+import * as fromSurvey from './survey.reducer';
+import * as surveyActions from '../actions/survey.actions';
+import { SURVEY, SURVEY_LIST } from '../../../test-data';
+
+describe('[Metamodel][Reducers] Survey reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromSurvey;
+        const action = { type: 'Unknown' };
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadSurveyList action should set surveyListIsLoading to true', () => {
+        const { initialState } = fromSurvey;
+        const action = surveyActions.loadSurveyList();
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.surveyListIsLoading).toEqual(true);
+        expect(state.surveyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSurveyListSuccess action should add survey list, set surveyListIsLoading to false and set surveyListIsLoaded to true', () => {
+        const { initialState } = fromSurvey;
+        const action = surveyActions.loadSurveyListSuccess({ surveys: SURVEY_LIST });
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('survey-one');
+        expect(state.ids).toContain('survey-two');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.surveyListIsLoading).toEqual(false);
+        expect(state.surveyListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadSurveyListFail action should set surveyListIsLoading to false', () => {
+        const { initialState } = fromSurvey;
+        const action = surveyActions.loadSurveyListFail();
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.surveyListIsLoading).toEqual(false);
+        expect(state.surveyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('addSurveySuccess action should add a survey', () => {
+        const { initialState } = fromSurvey;
+        const action = surveyActions.addSurveySuccess({ survey: SURVEY });
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('mySurvey');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.surveyListIsLoading).toEqual(false);
+        expect(state.surveyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('editSurveySuccess action should modify a survey', () => {
+        const initialState = {
+            ...fromSurvey.initialState,
+            ids: ['mySurvey'],
+            entities: { 'mySurvey': { ...SURVEY, label: 'label' }}
+        };
+        const action = surveyActions.editSurveySuccess({ survey: SURVEY });
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(1);
+        expect(state.ids).toContain('mySurvey');
+        expect(Object.keys(state.entities).length).toEqual(1);
+        expect(state.entities['mySurvey']).toEqual(SURVEY);
+        expect(state.surveyListIsLoading).toEqual(false);
+        expect(state.surveyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('deleteSurveySuccess action should modify a survey', () => {
+        const initialState = {
+            ...fromSurvey.initialState,
+            ids: ['mySurvey'],
+            entities: { 'mySurvey': SURVEY }
+        };
+        const action = surveyActions.deleteSurveySuccess({ survey: SURVEY });
+        const state = fromSurvey.surveyReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual( { });
+        expect(state.surveyListIsLoading).toEqual(false);
+        expect(state.surveyListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get surveyListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromSurvey.surveyReducer(undefined, action);
+
+        expect(fromSurvey.selectSurveyListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get surveyListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromSurvey.surveyReducer(undefined, action);
+
+        expect(fromSurvey.selectSurveyListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/survey.reducer.ts b/client/src/app/metamodel/reducers/survey.reducer.ts
index 610cd25423491752c6f23b5f8b5866a659bdc578..7514b7b3948785c2e5edb04553f0e817a0e2cedb 100644
--- a/client/src/app/metamodel/reducers/survey.reducer.ts
+++ b/client/src/app/metamodel/reducers/survey.reducer.ts
@@ -13,6 +13,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 import { Survey } from '../models';
 import * as surveyActions from '../actions/survey.actions';
 
+/**
+ * Interface for survey state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<Survey> {
     surveyListIsLoading: boolean;
     surveyListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const surveyReducer = createReducer(
     }),
     on(surveyActions.loadSurveyListSuccess, (state, { surveys }) => {
         return adapter.setAll(
-            surveys, 
-            { 
+            surveys,
+            {
                 ...state,
                 surveyListIsLoading: false,
                 surveyListIsLoaded: true
diff --git a/client/src/app/metamodel/reducers/table.reducer.spec.ts b/client/src/app/metamodel/reducers/table.reducer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9aff817fa8e807d7cff40cda6d4e6e9947958b86
--- /dev/null
+++ b/client/src/app/metamodel/reducers/table.reducer.spec.ts
@@ -0,0 +1,62 @@
+import { Action } from '@ngrx/store';
+
+import * as fromTable from './table.reducer';
+import * as tableActions from '../actions/table.actions';
+
+describe('[Metamodel][Reducers] Table reducer', () => {
+    it('unknown action should return the default state', () => {
+        const { initialState } = fromTable;
+        const action = { type: 'Unknown' };
+        const state = fromTable.tableReducer(initialState, action);
+        expect(state).toBe(initialState);
+    });
+
+    it('loadTableList action should set tableListIsLoading to true', () => {
+        const { initialState } = fromTable;
+        const action = tableActions.loadTableList({ idDatabase: 1 });
+        const state = fromTable.tableReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.tableListIsLoading).toEqual(true);
+        expect(state.tableListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadTableListSuccess action should add table list, set tableListIsLoading to false and set tableListIsLoaded to true', () => {
+        const { initialState } = fromTable;
+        const action = tableActions.loadTableListSuccess({ tables: ['table-one', 'table-two'] });
+        const state = fromTable.tableReducer(initialState, action);
+        expect(state.ids.length).toEqual(2);
+        expect(state.ids).toContain('table-one');
+        expect(state.ids).toContain('table-two');
+        expect(Object.keys(state.entities).length).toEqual(2);
+        expect(state.tableListIsLoading).toEqual(false);
+        expect(state.tableListIsLoaded).toEqual(true);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('loadTableListFail action should set tableListIsLoading to false', () => {
+        const { initialState } = fromTable;
+        const action = tableActions.loadTableListFail();
+        const state = fromTable.tableReducer(initialState, action);
+        expect(state.ids.length).toEqual(0);
+        expect(state.entities).toEqual({ });
+        expect(state.tableListIsLoading).toEqual(false);
+        expect(state.tableListIsLoaded).toEqual(false);
+        expect(state).not.toBe(initialState);
+    });
+
+    it('should get tableListIsLoading', () => {
+        const action = {} as Action;
+        const state =  fromTable.tableReducer(undefined, action);
+
+        expect(fromTable.selectTableListIsLoading(state)).toEqual(false);
+    });
+
+    it('should get tableListIsLoaded', () => {
+        const action = {} as Action;
+        const state = fromTable.tableReducer(undefined, action);
+
+        expect(fromTable.selectTableListIsLoaded(state)).toEqual(false);
+    });
+});
diff --git a/client/src/app/metamodel/reducers/table.reducer.ts b/client/src/app/metamodel/reducers/table.reducer.ts
index 24acb6e6c7aedfdd20c187a1e9ef784adec331cb..5b5f369bbc4cfff2e739f8a5363ffc4d2f41fc51 100644
--- a/client/src/app/metamodel/reducers/table.reducer.ts
+++ b/client/src/app/metamodel/reducers/table.reducer.ts
@@ -12,6 +12,11 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
 
 import * as tableActions from '../actions/table.actions';
 
+/**
+ * Interface for table state.
+ *
+ * @interface State
+ */
 export interface State extends EntityState<string> {
     tableListIsLoading: boolean;
     tableListIsLoaded: boolean;
@@ -38,8 +43,8 @@ export const tableReducer = createReducer(
     }),
     on(tableActions.loadTableListSuccess, (state, { tables }) => {
         return adapter.setAll(
-            tables, 
-            { 
+            tables,
+            {
                 ...state,
                 tableListIsLoading: false,
                 tableListIsLoaded: true
diff --git a/client/src/app/metamodel/selectors/attribute-distinct.selector.spec.ts b/client/src/app/metamodel/selectors/attribute-distinct.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..27fd525451e783a1c17893b5ac8e57e81fbe73c8
--- /dev/null
+++ b/client/src/app/metamodel/selectors/attribute-distinct.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as attributeDistinctSelector from './attribute-distinct.selector';
+import * as fromAttributeDistinct from '../reducers/attribute-distinct.reducer';
+
+describe('[Metamodel][Selector] AttributeDistinct selector', () => {
+    it('should get attributeDistinct state', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctState(state)).toEqual(state.metamodel.attributeDistinct);
+    });
+
+    it('should get attributeDistinct IDs', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctIds(state).length).toEqual(0);
+    });
+
+    it('should get attributeDistinct entities', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctEntities(state)).toEqual({ });
+    });
+
+    it('should get all attributeDistincts', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAllAttributeDistincts(state).length).toEqual(0);
+    });
+
+    it('should get attributeDistinct count', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctTotal(state)).toEqual(0);
+    });
+
+    it('should get attributeDistinctListIsLoading', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctListIsLoading(state)).toBe(false);
+    });
+
+    it('should get attributeDistinctListIsLoaded', () => {
+        const state = { metamodel: { attributeDistinct: { ...fromAttributeDistinct.initialState }}};
+        expect(attributeDistinctSelector.selectAttributeDistinctListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/attribute.selector.spec.ts b/client/src/app/metamodel/selectors/attribute.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28e634af25859de467cc826bc4db56098b4820ed
--- /dev/null
+++ b/client/src/app/metamodel/selectors/attribute.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as attributeSelector from './attribute.selector';
+import * as fromAttribute from '../reducers/attribute.reducer';
+
+describe('[Metamodel][Selector] Attribute selector', () => {
+    it('should get attribute state', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeState(state)).toEqual(state.metamodel.attribute);
+    });
+
+    it('should get attribute IDs', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeIds(state).length).toEqual(0);
+    });
+
+    it('should get attribute entities', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeEntities(state)).toEqual({ });
+    });
+
+    it('should get all attributes', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAllAttributes(state).length).toEqual(0);
+    });
+
+    it('should get attribute count', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeTotal(state)).toEqual(0);
+    });
+
+    it('should get attributeListIsLoading', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeListIsLoading(state)).toBe(false);
+    });
+
+    it('should get attributeListIsLoaded', () => {
+        const state = { metamodel: { attribute: { ...fromAttribute.initialState }}};
+        expect(attributeSelector.selectAttributeListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/column.selector.spec.ts b/client/src/app/metamodel/selectors/column.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..16e1076a498895aa301a43144e6dd360a0ed971b
--- /dev/null
+++ b/client/src/app/metamodel/selectors/column.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as columnSelector from './column.selector';
+import * as fromColumn from '../reducers/column.reducer';
+import { COLUMN } from '../../../test-data';
+
+describe('[Metamodel][Selector] Column selector', () => {
+    it('should get column state', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnState(state)).toEqual(state.metamodel.column);
+    });
+
+    it('should get column IDs', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnIds(state).length).toEqual(0);
+    });
+
+    it('should get column entities', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnEntities(state)).toEqual({ });
+    });
+
+    it('should get all columns', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectAllColumns(state).length).toEqual(0);
+    });
+
+    it('should get column count', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnTotal(state)).toEqual(0);
+    });
+
+    it('should get columnListIsLoading', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnListIsLoading(state)).toBe(false);
+    });
+
+    it('should get columnListIsLoaded', () => {
+        const state = { metamodel: { column: { ...fromColumn.initialState }}};
+        expect(columnSelector.selectColumnListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get column by route', () => {
+        const state = {
+            router: { state: { params: { name: 'myCol' }}},
+            metamodel: {
+                column: {
+                    ...fromColumn.initialState,
+                    ids: ['myCol'],
+                    entities: { 'myCol': COLUMN }
+                }
+            }
+        };
+        expect(columnSelector.selectColumnByRouteName(state)).toEqual(COLUMN);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/criteria-family.selector.spec.ts b/client/src/app/metamodel/selectors/criteria-family.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43dadd8477fc0a1b334d074f367ed404a43f3f34
--- /dev/null
+++ b/client/src/app/metamodel/selectors/criteria-family.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as criteriaFamilySelector from './criteria-family.selector';
+import * as fromCriteriaFamily from '../reducers/criteria-family.reducer';
+
+describe('[Metamodel][Selector] CriteriaFamily selector', () => {
+    it('should get criteriaFamily state', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyState(state)).toEqual(state.metamodel.criteriaFamily);
+    });
+
+    it('should get criteriaFamily IDs', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyIds(state).length).toEqual(0);
+    });
+
+    it('should get criteriaFamily entities', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyEntities(state)).toEqual({ });
+    });
+
+    it('should get all criteriaFamilies', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectAllCriteriaFamilies(state).length).toEqual(0);
+    });
+
+    it('should get criteriaFamily count', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyTotal(state)).toEqual(0);
+    });
+
+    it('should get criteriaFamilyListIsLoading', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyListIsLoading(state)).toBe(false);
+    });
+
+    it('should get criteriaFamilyListIsLoaded', () => {
+        const state = { metamodel: { criteriaFamily: { ...fromCriteriaFamily.initialState }}};
+        expect(criteriaFamilySelector.selectCriteriaFamilyListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/database.selector.spec.ts b/client/src/app/metamodel/selectors/database.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..149b4fd41d712d6c690c8819d08e8dbc0544aa02
--- /dev/null
+++ b/client/src/app/metamodel/selectors/database.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as databaseSelector from './database.selector';
+import * as fromDatabase from '../reducers/database.reducer';
+import { DATABASE } from '../../../test-data';
+
+describe('[Metamodel][Selector] Database selector', () => {
+    it('should get database state', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseState(state)).toEqual(state.metamodel.database);
+    });
+
+    it('should get database IDs', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseIds(state).length).toEqual(0);
+    });
+
+    it('should get database entities', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseEntities(state)).toEqual({ });
+    });
+
+    it('should get all databases', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectAllDatabases(state).length).toEqual(0);
+    });
+
+    it('should get database count', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseTotal(state)).toEqual(0);
+    });
+
+    it('should get databaseListIsLoading', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseListIsLoading(state)).toBe(false);
+    });
+
+    it('should get databaseListIsLoaded', () => {
+        const state = { metamodel: { database: { ...fromDatabase.initialState }}};
+        expect(databaseSelector.selectDatabaseListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get database by route', () => {
+        const state = {
+            router: { state: { params: { id: 1 }}},
+            metamodel: {
+                database: {
+                    ...fromDatabase.initialState,
+                    ids: [1],
+                    entities: { 1: DATABASE }
+                }
+            }
+        };
+        expect(databaseSelector.selectDatabaseByRouteId(state)).toEqual(DATABASE);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/dataset-family.selector.spec.ts b/client/src/app/metamodel/selectors/dataset-family.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c7d7e69bcbb3dfd00401c3a470c935519d3081f
--- /dev/null
+++ b/client/src/app/metamodel/selectors/dataset-family.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as datasetFamilySelector from './dataset-family.selector';
+import * as fromDatasetFamily from '../reducers/dataset-family.reducer';
+
+describe('[Metamodel][Selector] DatasetFamily selector', () => {
+    it('should get datasetFamily state', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyState(state)).toEqual(state.metamodel.datasetFamily);
+    });
+
+    it('should get datasetFamily IDs', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyIds(state).length).toEqual(0);
+    });
+
+    it('should get datasetFamily entities', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyEntities(state)).toEqual({ });
+    });
+
+    it('should get all datasetFamilies', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectAllDatasetFamilies(state).length).toEqual(0);
+    });
+
+    it('should get datasetFamily count', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyTotal(state)).toEqual(0);
+    });
+
+    it('should get datasetFamilyListIsLoading', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyListIsLoading(state)).toBe(false);
+    });
+
+    it('should get datasetFamilyListIsLoaded', () => {
+        const state = { metamodel: { datasetFamily: { ...fromDatasetFamily.initialState }}};
+        expect(datasetFamilySelector.selectDatasetFamilyListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/dataset.selector.spec.ts b/client/src/app/metamodel/selectors/dataset.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9551f9d199823569e8aac551348f3037fd37a80a
--- /dev/null
+++ b/client/src/app/metamodel/selectors/dataset.selector.spec.ts
@@ -0,0 +1,76 @@
+import * as datasetSelector from './dataset.selector';
+import * as fromDataset from '../reducers/dataset.reducer';
+import { DATASET, DATASET_LIST } from '../../../test-data';
+
+describe('[Metamodel][Selector] Dataset selector', () => {
+    it('should get dataset state', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetState(state)).toEqual(state.metamodel.dataset);
+    });
+
+    it('should get dataset IDs', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetIds(state).length).toEqual(0);
+    });
+
+    it('should get dataset entities', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetEntities(state)).toEqual({ });
+    });
+
+    it('should get all datasets', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectAllDatasets(state).length).toEqual(0);
+    });
+
+    it('should get dataset count', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetTotal(state)).toEqual(0);
+    });
+
+    it('should get datasetListIsLoading', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetListIsLoading(state)).toBe(false);
+    });
+
+    it('should get datasetListIsLoaded', () => {
+        const state = { metamodel: { dataset: { ...fromDataset.initialState }}};
+        expect(datasetSelector.selectDatasetListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get dataset by route', () => {
+        const state = {
+            router: { state: { params: { dname: 'myDataset' }}},
+            metamodel: {
+                dataset: {
+                    ...fromDataset.initialState,
+                    ids: ['myDataset'],
+                    entities: { 'myDataset': DATASET }
+                }
+            }
+        };
+        expect(datasetSelector.selectDatasetByRouteName(state)).toEqual(DATASET);
+    });
+
+    it('should get dataset name by route', () => {
+        const state = {router: { state: { params: { dname: 'myDataset' }}}};
+        expect(datasetSelector.selectDatasetNameByRoute(state)).toEqual('myDataset');
+    });
+
+    it('should get datasets with cone search', () => {
+        const state = {
+            metamodel: {
+                dataset: {
+                    ...fromDataset.initialState,
+                    ids: ['myDataset', 'anotherDataset'],
+                    entities: {
+                        'myDataset': DATASET_LIST[0],
+                        'anotherDataset': DATASET_LIST[1]
+                    }
+                }
+            }
+        };
+        expect(datasetSelector.selectAllConeSearchDatasets(state).length).toEqual(1);
+        expect(datasetSelector.selectAllConeSearchDatasets(state)).toContain(DATASET_LIST[0]);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/group.selector.spec.ts b/client/src/app/metamodel/selectors/group.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc8366f5bb25e7e9fb097c7efb3d63de7fbd27a7
--- /dev/null
+++ b/client/src/app/metamodel/selectors/group.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as groupSelector from './group.selector';
+import * as fromGroup from '../reducers/group.reducer';
+import { GROUP } from '../../../test-data';
+
+describe('[Metamodel][Selector] Group selector', () => {
+    it('should get group state', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupState(state)).toEqual(state.metamodel.group);
+    });
+
+    it('should get group IDs', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupIds(state).length).toEqual(0);
+    });
+
+    it('should get group entities', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupEntities(state)).toEqual({ });
+    });
+
+    it('should get all groups', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectAllGroups(state).length).toEqual(0);
+    });
+
+    it('should get group count', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupTotal(state)).toEqual(0);
+    });
+
+    it('should get groupListIsLoading', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupListIsLoading(state)).toBe(false);
+    });
+
+    it('should get groupListIsLoaded', () => {
+        const state = { metamodel: { group: { ...fromGroup.initialState }}};
+        expect(groupSelector.selectGroupListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get group ID by route', () => {
+        const state = {
+            router: { state: { params: { id: 1 }}},
+            metamodel: {
+                group: {
+                    ...fromGroup.initialState,
+                    ids: [1],
+                    entities: { 1: GROUP }
+                }
+            }
+        };
+        expect(groupSelector.selectGroupByRouteId(state)).toEqual(GROUP);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/instance.selector.spec.ts b/client/src/app/metamodel/selectors/instance.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..137da338d11f07ac3016e8c96cdbfc2dd1fd36af
--- /dev/null
+++ b/client/src/app/metamodel/selectors/instance.selector.spec.ts
@@ -0,0 +1,59 @@
+import * as instanceSelector from './instance.selector';
+import * as fromInstance from '../reducers/instance.reducer';
+import { INSTANCE } from '../../../test-data';
+
+describe('[Metamodel][Selector] Instance selector', () => {
+    it('should get instance state', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceState(state)).toEqual(state.metamodel.instance);
+    });
+
+    it('should get instance IDs', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceIds(state).length).toEqual(0);
+    });
+
+    it('should get instance entities', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceEntities(state)).toEqual({ });
+    });
+
+    it('should get all instances', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectAllInstances(state).length).toEqual(0);
+    });
+
+    it('should get instance count', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceTotal(state)).toEqual(0);
+    });
+
+    it('should get instanceListIsLoading', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceListIsLoading(state)).toBe(false);
+    });
+
+    it('should get instanceListIsLoaded', () => {
+        const state = { metamodel: { instance: { ...fromInstance.initialState }}};
+        expect(instanceSelector.selectInstanceListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get instance by route', () => {
+        const state = {
+            router: { state: { params: { iname: 'myInstance' }}},
+            metamodel: {
+                instance: {
+                    ...fromInstance.initialState,
+                    ids: ['myInstance'],
+                    entities: { 'myInstance': INSTANCE }
+                }
+            }
+        };
+        expect(instanceSelector.selectInstanceByRouteName(state)).toEqual(INSTANCE);
+    });
+
+    it('should get instance name by route', () => {
+        const state = { router: { state: { params: { iname: 'myInstance' }}}};
+        expect(instanceSelector.selectInstanceNameByRoute(state)).toEqual('myInstance');
+    });
+});
diff --git a/client/src/app/metamodel/selectors/output-category.selector.spec.ts b/client/src/app/metamodel/selectors/output-category.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eeb4e606b21e835b7bc8b60c8fcf626893cfd99c
--- /dev/null
+++ b/client/src/app/metamodel/selectors/output-category.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as outputCategorySelector from './output-category.selector';
+import * as fromOutputCategory from '../reducers/output-category.reducer';
+
+describe('[Metamodel][Selector] OutputCategory selector', () => {
+    it('should get outputCategory state', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryState(state)).toEqual(state.metamodel.outputCategory);
+    });
+
+    it('should get outputCategory IDs', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryIds(state).length).toEqual(0);
+    });
+
+    it('should get outputCategory entities', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryEntities(state)).toEqual({ });
+    });
+
+    it('should get all outputCategories', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectAllOutputCategories(state).length).toEqual(0);
+    });
+
+    it('should get outputCategory count', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryTotal(state)).toEqual(0);
+    });
+
+    it('should get outputCategoryListIsLoading', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryListIsLoading(state)).toBe(false);
+    });
+
+    it('should get outputCategoryListIsLoaded', () => {
+        const state = { metamodel: { outputCategory: { ...fromOutputCategory.initialState }}};
+        expect(outputCategorySelector.selectOutputCategoryListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/output-family.selector.spec.ts b/client/src/app/metamodel/selectors/output-family.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58e18fd61ff05c9715e587eb9df8bbf75e3496e9
--- /dev/null
+++ b/client/src/app/metamodel/selectors/output-family.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as outputFamilySelector from './output-family.selector';
+import * as fromOutputFamily from '../reducers/output-family.reducer';
+
+describe('[Metamodel][Selector] Output family selector', () => {
+    it('should get outputFamily state', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyState(state)).toEqual(state.metamodel.outputFamily);
+    });
+
+    it('should get outputFamily IDs', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyIds(state).length).toEqual(0);
+    });
+
+    it('should get outputFamily entities', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyEntities(state)).toEqual({ });
+    });
+
+    it('should get all outputFamilies', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectAllOutputFamilies(state).length).toEqual(0);
+    });
+
+    it('should get outputFamily count', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyTotal(state)).toEqual(0);
+    });
+
+    it('should get outputFamilyListIsLoading', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyListIsLoading(state)).toBe(false);
+    });
+
+    it('should get outputFamilyListIsLoaded', () => {
+        const state = { metamodel: { outputFamily: { ...fromOutputFamily.initialState }}};
+        expect(outputFamilySelector.selectOutputFamilyListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/root-directory.selector.spec.ts b/client/src/app/metamodel/selectors/root-directory.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab145eb08a0668c0575959ccedeca1561832f3ea
--- /dev/null
+++ b/client/src/app/metamodel/selectors/root-directory.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as rootDirectorySelector from './root-directory.selector';
+import * as fromRootDirectory from '../reducers/root-directory.reducer';
+
+describe('[Metamodel][Selector] Root Directory selector', () => {
+    it('should get rootDirectory state', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectRootDirectoryState(state)).toEqual(state.metamodel.rootDirectory);
+    });
+
+    it('should get file info IDs', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectFileInfoIds(state).length).toEqual(0);
+    });
+
+    it('should get file info entities', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectFileInfoEntities(state)).toEqual({ });
+    });
+
+    it('should get all file info', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectAllFileInfo(state).length).toEqual(0);
+    });
+
+    it('should get file info count', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectFileInfoTotal(state)).toEqual(0);
+    });
+
+    it('should get rootDirectoryIsLoading', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectRootDirectoryIsLoading(state)).toBe(false);
+    });
+
+    it('should get rootDirectoryIsLoading', () => {
+        const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}};
+        expect(rootDirectorySelector.selectRootDirectoryIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/select-option.selector.spec.ts b/client/src/app/metamodel/selectors/select-option.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e9933201627e0a6d2d3e918d5e99e68b1f6f1142
--- /dev/null
+++ b/client/src/app/metamodel/selectors/select-option.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as selectOptionSelector from './select-option.selector';
+import * as fromSelectOption from '../reducers/select-option.reducer';
+import { SELECT_OPTION } from '../../../test-data';
+
+describe('[Metamodel][Selector] Select option selector', () => {
+    it('should get selectOption state', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionState(state)).toEqual(state.metamodel.selectOption);
+    });
+
+    it('should get selectOption IDs', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionIds(state).length).toEqual(0);
+    });
+
+    it('should get selectOption entities', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionEntities(state)).toEqual({ });
+    });
+
+    it('should get all selectOption', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectAllSelectOptions(state).length).toEqual(0);
+    });
+
+    it('should get selectOption count', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionTotal(state)).toEqual(0);
+    });
+
+    it('should get selectOptionListIsLoading', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionListIsLoading(state)).toBe(false);
+    });
+
+    it('should get selectOptionListIsLoaded', () => {
+        const state = { metamodel: { selectOption: { ...fromSelectOption.initialState }}};
+        expect(selectOptionSelector.selectSelectOptionListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get select option by select name', () => {
+        const state = {
+            router: { state: { params: { select: 'name_one' }}},
+            metamodel: {
+                selectOption: {
+                    ...fromSelectOption.initialState,
+                    ids: [1],
+                    entities: { 1: SELECT_OPTION }
+                }
+            }
+        };
+        expect(selectOptionSelector.getOptionBySelectName(state)).toEqual([SELECT_OPTION]);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/select.selector.spec.ts b/client/src/app/metamodel/selectors/select.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..227e38e6f8475fa96b6319b7defea9309d896b1c
--- /dev/null
+++ b/client/src/app/metamodel/selectors/select.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as selectSelector from './select.selector';
+import * as fromSelect from '../reducers/select.reducer';
+import { SELECT } from '../../../test-data';
+
+describe('[Metamodel][Selector] Select selector', () => {
+    it('should get select state', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectState(state)).toEqual(state.metamodel.select);
+    });
+
+    it('should get select IDs', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectIds(state).length).toEqual(0);
+    });
+
+    it('should get select entities', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectEntities(state)).toEqual({ });
+    });
+
+    it('should get all selects', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectAllSelects(state).length).toEqual(0);
+    });
+
+    it('should get select count', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectTotal(state)).toEqual(0);
+    });
+
+    it('should get selectListIsLoading', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectListIsLoading(state)).toBe(false);
+    });
+
+    it('should get selectListIsLoaded', () => {
+        const state = { metamodel: { select: { ...fromSelect.initialState }}};
+        expect(selectSelector.selectSelectListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get select by route', () => {
+        const state = {
+            router: { state: { params: { select: 'mySelect' }}},
+            metamodel: {
+                select: {
+                    ...fromSelect.initialState,
+                    ids: ['mySelect'],
+                    entities: { 'mySelect': SELECT }
+                }
+            }
+        };
+        expect(selectSelector.getSelectByRouteName(state)).toEqual(SELECT);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/survey.selector.spec.ts b/client/src/app/metamodel/selectors/survey.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..21885ff6498d766169396f4650abdc7efca80aa0
--- /dev/null
+++ b/client/src/app/metamodel/selectors/survey.selector.spec.ts
@@ -0,0 +1,54 @@
+import * as surveySelector from './survey.selector';
+import * as fromSurvey from '../reducers/survey.reducer';
+import { SURVEY } from '../../../test-data';
+
+describe('[Metamodel][Selector] Survey selector', () => {
+    it('should get survey state', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyState(state)).toEqual(state.metamodel.survey);
+    });
+
+    it('should get survey IDs', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyIds(state).length).toEqual(0);
+    });
+
+    it('should get survey entities', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyEntities(state)).toEqual({ });
+    });
+
+    it('should get all surveys', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectAllSurveys(state).length).toEqual(0);
+    });
+
+    it('should get survey count', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyTotal(state)).toEqual(0);
+    });
+
+    it('should get surveyListIsLoading', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyListIsLoading(state)).toBe(false);
+    });
+
+    it('should get surveyListIsLoaded', () => {
+        const state = { metamodel: { survey: { ...fromSurvey.initialState }}};
+        expect(surveySelector.selectSurveyListIsLoaded(state)).toBe(false);
+    });
+
+    it('should get survey by route', () => {
+        const state = {
+            router: { state: { params: { name: 'survey-one' }}},
+            metamodel: {
+                survey: {
+                    ...fromSurvey.initialState,
+                    ids: ['survey-one'],
+                    entities: { 'survey-one': SURVEY }
+                }
+            }
+        };
+        expect(surveySelector.selectSurveyByRouteName(state)).toEqual(SURVEY);
+    });
+});
diff --git a/client/src/app/metamodel/selectors/table.selector.spec.ts b/client/src/app/metamodel/selectors/table.selector.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3cb5f073b8e212286f01b724fb3dfd4036afc7de
--- /dev/null
+++ b/client/src/app/metamodel/selectors/table.selector.spec.ts
@@ -0,0 +1,39 @@
+import * as tableSelector from './table.selector';
+import * as fromTable from '../reducers/table.reducer';
+
+describe('[Metamodel][Selector] Table selector', () => {
+    it('should get table state', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableState(state)).toEqual(state.metamodel.table);
+    });
+
+    it('should get table IDs', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableIds(state).length).toEqual(0);
+    });
+
+    it('should get table entities', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableEntities(state)).toEqual({ });
+    });
+
+    it('should get all tables', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectAllTables(state).length).toEqual(0);
+    });
+
+    it('should get table count', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableTotal(state)).toEqual(0);
+    });
+
+    it('should get tableListIsLoading', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableListIsLoading(state)).toBe(false);
+    });
+
+    it('should get tableListIsLoaded', () => {
+        const state = { metamodel: { table: { ...fromTable.initialState }}};
+        expect(tableSelector.selectTableListIsLoaded(state)).toBe(false);
+    });
+});
diff --git a/client/src/app/metamodel/services/attribute-distinct.service.spec.ts b/client/src/app/metamodel/services/attribute-distinct.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a9380a030fdbefd16bc068096767ea426a6d0bd7
--- /dev/null
+++ b/client/src/app/metamodel/services/attribute-distinct.service.spec.ts
@@ -0,0 +1,41 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { AttributeDistinctService } from './attribute-distinct.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { ATTRIBUTE } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] AttributeDistinctService', () => {
+    let service: AttributeDistinctService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                AttributeDistinctService
+            ]
+        });
+        service = TestBed.inject(AttributeDistinctService);
+    });
+
+    it('#retrieveAttributeDistinctList() should return an Observable<string[]>',
+        inject([HttpTestingController, AttributeDistinctService],(httpMock: HttpTestingController, service: AttributeDistinctService) => {
+                const mockResponse = [];
+
+                service.retrieveAttributeDistinctList('myDataset', ATTRIBUTE).subscribe((event: string[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/attribute/1/distinct');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/attribute-distinct.service.ts b/client/src/app/metamodel/services/attribute-distinct.service.ts
index 8051c13d5d362582e287787db0130146779d9a82..1dc4ba7ea000fc908b4500cd7383f66a7be2028a 100644
--- a/client/src/app/metamodel/services/attribute-distinct.service.ts
+++ b/client/src/app/metamodel/services/attribute-distinct.service.ts
@@ -15,10 +15,22 @@ import { Observable } from 'rxjs';
 import { Attribute } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Attribute distinct service.
+ */
 @Injectable()
 export class AttributeDistinctService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves distinct attribute list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {Attribute} attribute - The attribute.
+     *
+     * @return Observable<string[]>
+     */
     retrieveAttributeDistinctList(datasetName: string, attribute: Attribute): Observable<string[]> {
         return this.http.get<string[]>(`${this.config.apiUrl}/dataset/${datasetName}/attribute/${attribute.id}/distinct`);
     }
diff --git a/client/src/app/metamodel/services/attribute.service.spec.ts b/client/src/app/metamodel/services/attribute.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f1882248def816806b542e38e8f7ccc318a86cf
--- /dev/null
+++ b/client/src/app/metamodel/services/attribute.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { AttributeService } from './attribute.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Attribute } from '../models';
+import { ATTRIBUTE } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] AttributeService', () => {
+    let service: AttributeService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                AttributeService
+            ]
+        });
+        service = TestBed.inject(AttributeService);
+    });
+
+    it('#retrieveAttributeList() should return an Observable<Attribute[]>',
+        inject([HttpTestingController, AttributeService],(httpMock: HttpTestingController, service: AttributeService) => {
+                const mockResponse = [];
+
+                service.retrieveAttributeList('myDataset').subscribe((event: Attribute[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/attribute');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addAttribute() should return an Observable<Attribute>',
+        inject([HttpTestingController, AttributeService],(httpMock: HttpTestingController, service: AttributeService) => {
+                const mockResponse = ATTRIBUTE;
+
+                service.addAttribute('myDataset', ATTRIBUTE).subscribe((event: Attribute) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/attribute');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editAttribute() should return an Observable<Attribute>',
+        inject([HttpTestingController, AttributeService],(httpMock: HttpTestingController, service: AttributeService) => {
+                const mockResponse = ATTRIBUTE;
+
+                service.editAttribute('myDataset', ATTRIBUTE).subscribe((event: Attribute) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/attribute/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteAttribute() should return an Observable<object>',
+        inject([HttpTestingController, AttributeService],(httpMock: HttpTestingController, service: AttributeService) => {
+                const mockResponse = {};
+
+                service.deleteAttribute('myDataset', ATTRIBUTE).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/attribute/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/attribute.service.ts b/client/src/app/metamodel/services/attribute.service.ts
index 50fa14d9126f081f891caae53ad5b02da1ea0827..34f056b1dbb01da3662b21fa92b476049d6a233d 100644
--- a/client/src/app/metamodel/services/attribute.service.ts
+++ b/client/src/app/metamodel/services/attribute.service.ts
@@ -15,23 +15,58 @@ import { Observable } from 'rxjs';
 import { Attribute } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Attribute service.
+ */
 @Injectable()
 export class AttributeService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves attribute list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<Attribute[]>
+     */
     retrieveAttributeList(datasetName: string): Observable<Attribute[]> {
         return this.http.get<Attribute[]>(`${this.config.apiUrl}/dataset/${datasetName}/attribute`);
     }
 
+    /**
+     * Adds a new attribute to the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {Attribute} attribute - The attribute.
+     *
+     * @return Observable<Attribute>
+     */
     addAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> {
         return this.http.post<Attribute>(`${this.config.apiUrl}/dataset/${datasetName}/attribute`, attribute);
     }
 
+    /**
+     * Modifies an attribute to the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {Attribute} attribute - The attribute.
+     *
+     * @return Observable<Attribute>
+     */
     editAttribute(datasetName: string, attribute: Attribute): Observable<Attribute> {
         return this.http.put<Attribute>(`${this.config.apiUrl}/dataset/${datasetName}/attribute/${attribute.id}`, attribute);
     }
 
-    deleteAttribute(datasetName: string, attribute: Attribute) {
-        return this.http.delete(`${this.config.apiUrl}/dataset/${datasetName}/attribute/${attribute.id}`); 
+    /**
+     * Removes an attribute to the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {Attribute} attribute - The attribute.
+     *
+     * @return Observable<object>
+     */
+    deleteAttribute(datasetName: string, attribute: Attribute): Observable<object> {
+        return this.http.delete(`${this.config.apiUrl}/dataset/${datasetName}/attribute/${attribute.id}`);
     }
 }
diff --git a/client/src/app/metamodel/services/column.service.spec.ts b/client/src/app/metamodel/services/column.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c13e511743d17a5aa19f259c3738daa8db670acd
--- /dev/null
+++ b/client/src/app/metamodel/services/column.service.spec.ts
@@ -0,0 +1,41 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { ColumnService } from './column.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Column } from '../models';
+
+describe('[Instance][Metamodel][Services] ColumnService', () => {
+    let service: ColumnService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                ColumnService
+            ]
+        });
+        service = TestBed.inject(ColumnService);
+    });
+
+    it('#retrieveColumns() should return an Observable<Column[]>',
+        inject([HttpTestingController, ColumnService],(httpMock: HttpTestingController, service: ColumnService) => {
+                const mockResponse = [];
+
+                service.retrieveColumns('myDataset').subscribe((event: Column[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/column');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/column.service.ts b/client/src/app/metamodel/services/column.service.ts
index 16b51ddc6e00e112ebf754ee128deea6abfdb300..6f97e64d4f2fde6456fc0315f477cda2ef3886b4 100644
--- a/client/src/app/metamodel/services/column.service.ts
+++ b/client/src/app/metamodel/services/column.service.ts
@@ -15,10 +15,21 @@ import { Observable } from 'rxjs';
 import { Column } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Column service.
+ */
 @Injectable()
 export class ColumnService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves column list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<Column[]>
+     */
     retrieveColumns(datasetName: string): Observable<Column[]> {
         return this.http.get<Column[]>(`${this.config.apiUrl}/dataset/${datasetName}/column`);
     }
diff --git a/client/src/app/metamodel/services/criteria-family.service.spec.ts b/client/src/app/metamodel/services/criteria-family.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee25ed5e899ac73431c99c1311a070334270a77a
--- /dev/null
+++ b/client/src/app/metamodel/services/criteria-family.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { CriteriaFamilyService } from './criteria-family.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { CriteriaFamily } from '../models';
+import { CRITERIA_FAMILY } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] CriteriaFamilyService', () => {
+    let service: CriteriaFamilyService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                CriteriaFamilyService
+            ]
+        });
+        service = TestBed.inject(CriteriaFamilyService);
+    });
+
+    it('#retrieveCriteriaFamilyList() should return an Observable<CriteriaFamily[]>',
+        inject([HttpTestingController, CriteriaFamilyService],(httpMock: HttpTestingController, service: CriteriaFamilyService) => {
+                const mockResponse = [];
+
+                service.retrieveCriteriaFamilyList('myDataset').subscribe((event: CriteriaFamily[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/criteria-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addCriteriaFamily() should return an Observable<CriteriaFamily>',
+        inject([HttpTestingController, CriteriaFamilyService],(httpMock: HttpTestingController, service: CriteriaFamilyService) => {
+                const mockResponse = CRITERIA_FAMILY;
+
+                service.addCriteriaFamily('myDataset', CRITERIA_FAMILY).subscribe((event: CriteriaFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/criteria-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editCriteriaFamily() should return an Observable<CriteriaFamily>',
+        inject([HttpTestingController, CriteriaFamilyService],(httpMock: HttpTestingController, service: CriteriaFamilyService) => {
+                const mockResponse = CRITERIA_FAMILY;
+
+                service.editCriteriaFamily(CRITERIA_FAMILY).subscribe((event: CriteriaFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/criteria-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteCriteriaFamily() should return an Observable<object>',
+        inject([HttpTestingController, CriteriaFamilyService],(httpMock: HttpTestingController, service: CriteriaFamilyService) => {
+                const mockResponse = {};
+
+                service.deleteCriteriaFamily(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/criteria-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/criteria-family.service.ts b/client/src/app/metamodel/services/criteria-family.service.ts
index ece1eea2fad401567e6250b4f4a522478b7946bf..5b1d533b6863a0494332a42f3e5407379f950e81 100644
--- a/client/src/app/metamodel/services/criteria-family.service.ts
+++ b/client/src/app/metamodel/services/criteria-family.service.ts
@@ -15,23 +15,56 @@ import { Observable } from 'rxjs';
 import { CriteriaFamily } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Criteria family service.
+ */
 @Injectable()
 export class CriteriaFamilyService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves criteria family list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<CriteriaFamily[]>
+     */
     retrieveCriteriaFamilyList(datasetName: string): Observable<CriteriaFamily[]> {
         return this.http.get<CriteriaFamily[]>(`${this.config.apiUrl}/dataset/${datasetName}/criteria-family`);
     }
 
+    /**
+     * Adds a new criteria family for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {CriteriaFamily} newCriteriaFamily - The criteria family.
+     *
+     * @return Observable<CriteriaFamily>
+     */
     addCriteriaFamily(datasetName: string, newCriteriaFamily: CriteriaFamily): Observable<CriteriaFamily> {
         return this.http.post<CriteriaFamily>(`${this.config.apiUrl}/dataset/${datasetName}/criteria-family`, newCriteriaFamily);
     }
 
+    /**
+     * Modifies a criteria family.
+     *
+     * @param  {CriteriaFamily} criteriaFamily - The criteria family.
+     *
+     * @return Observable<CriteriaFamily>
+     */
     editCriteriaFamily(criteriaFamily: CriteriaFamily): Observable<CriteriaFamily> {
         return this.http.put<CriteriaFamily>(`${this.config.apiUrl}/criteria-family/${criteriaFamily.id}`, criteriaFamily);
     }
 
-    deleteCriteriaFamily(criteriaFamilyId: number) {
+    /**
+     * Removes a criteria family.
+     *
+     * @param  {number} criteriaFamilyId - The criteria family ID.
+     *
+     * @return Observable<object>
+     */
+    deleteCriteriaFamily(criteriaFamilyId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/criteria-family/${criteriaFamilyId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/database.service.spec.ts b/client/src/app/metamodel/services/database.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a3c34b8d64c60e2d64cbf0031fa3411cbf70269f
--- /dev/null
+++ b/client/src/app/metamodel/services/database.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { DatabaseService } from './database.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Database } from '../models';
+import { DATABASE } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] DatabaseService', () => {
+    let service: DatabaseService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                DatabaseService
+            ]
+        });
+        service = TestBed.inject(DatabaseService);
+    });
+
+    it('#retrieveDatabaseList() should return an Observable<Database[]>',
+        inject([HttpTestingController, DatabaseService],(httpMock: HttpTestingController, service: DatabaseService) => {
+                const mockResponse = [];
+
+                service.retrieveDatabaseList().subscribe((event: Database[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/database');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addDatabase() should return an Observable<Database>',
+        inject([HttpTestingController, DatabaseService],(httpMock: HttpTestingController, service: DatabaseService) => {
+                const mockResponse = DATABASE;
+
+                service.addDatabase(DATABASE).subscribe((event: Database) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/database');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editDatabase() should return an Observable<Database>',
+        inject([HttpTestingController, DatabaseService],(httpMock: HttpTestingController, service: DatabaseService) => {
+                const mockResponse = DATABASE;
+
+                service.editDatabase(DATABASE).subscribe((event: Database) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/database/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteDatabase() should return an Observable<object>',
+        inject([HttpTestingController, DatabaseService],(httpMock: HttpTestingController, service: DatabaseService) => {
+                const mockResponse = {};
+
+                service.deleteDatabase(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/database/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/database.service.ts b/client/src/app/metamodel/services/database.service.ts
index b24141715e62930397ffd247013af15c712bda86..a17665a551b297c2e6f7f2a33ff20a150383f09b 100644
--- a/client/src/app/metamodel/services/database.service.ts
+++ b/client/src/app/metamodel/services/database.service.ts
@@ -15,23 +15,53 @@ import { Observable } from 'rxjs';
 import { Database } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Database service.
+ */
 @Injectable()
 export class DatabaseService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves database list.
+     *
+     * @return Observable<Database[]>
+     */
     retrieveDatabaseList(): Observable<Database[]> {
         return this.http.get<Database[]>(`${this.config.apiUrl}/database`);
     }
 
+    /**
+     * Adds a new database.
+     *
+     * @param  {Database} newDatabase - The database.
+     *
+     * @return Observable<Database>
+     */
     addDatabase(newDatabase: Database): Observable<Database> {
         return this.http.post<Database>(`${this.config.apiUrl}/database`, newDatabase);
     }
 
+    /**
+     * Modifies a database.
+     *
+     * @param  {Database} database - The database.
+     *
+     * @return Observable<Database>
+     */
     editDatabase(database: Database): Observable<Database> {
         return this.http.put<Database>(`${this.config.apiUrl}/database/${database.id}`, database);
     }
 
-    deleteDatabase(databaseId: number) {
+    /**
+     * Removes a database.
+     *
+     * @param  {number} databaseId - The database ID.
+     *
+     * @return Observable<object>
+     */
+    deleteDatabase(databaseId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/database/${databaseId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/dataset-family.service.spec.ts b/client/src/app/metamodel/services/dataset-family.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..59a3ad499185445508fabd3a115efc710881f39f
--- /dev/null
+++ b/client/src/app/metamodel/services/dataset-family.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { DatasetFamilyService } from './dataset-family.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { DatasetFamily } from '../models';
+import { DATASET_FAMILY, GROUP } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] DatasetFamilyService', () => {
+    let service: DatasetFamilyService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                DatasetFamilyService
+            ]
+        });
+        service = TestBed.inject(DatasetFamilyService);
+    });
+
+    it('#retrieveDatasetFamilyList() should return an Observable<DatasetFamily[]>',
+        inject([HttpTestingController, DatasetFamilyService],(httpMock: HttpTestingController, service: DatasetFamilyService) => {
+                const mockResponse = [];
+
+                service.retrieveDatasetFamilyList('myInstance').subscribe((event: DatasetFamily[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/instance/myInstance/dataset-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addDatasetFamily() should return an Observable<DatasetFamily>',
+        inject([HttpTestingController, DatasetFamilyService],(httpMock: HttpTestingController, service: DatasetFamilyService) => {
+                const mockResponse = DATASET_FAMILY;
+
+                service.addDatasetFamily('myInstance', DATASET_FAMILY).subscribe((event: DatasetFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/instance/myInstance/dataset-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editDatasetFamily() should return an Observable<DatasetFamily>',
+        inject([HttpTestingController, DatasetFamilyService],(httpMock: HttpTestingController, service: DatasetFamilyService) => {
+                const mockResponse = DATASET_FAMILY;
+
+                service.editDatasetFamily(DATASET_FAMILY).subscribe((event: DatasetFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteDatasetFamily() should return an Observable<object>',
+        inject([HttpTestingController, DatasetFamilyService],(httpMock: HttpTestingController, service: DatasetFamilyService) => {
+                const mockResponse = {};
+
+                service.deleteDatasetFamily(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/dataset-family.service.ts b/client/src/app/metamodel/services/dataset-family.service.ts
index c7385f9c19b0723aba38d22f2dd42a9fa6356d36..099f2315dfee134c4600ec1917590362288bcb21 100644
--- a/client/src/app/metamodel/services/dataset-family.service.ts
+++ b/client/src/app/metamodel/services/dataset-family.service.ts
@@ -15,23 +15,56 @@ import { Observable } from 'rxjs';
 import { DatasetFamily } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Dataset family service.
+ */
 @Injectable()
 export class DatasetFamilyService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves dataset families for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     *
+     * @return Observable<DatasetFamily>
+     */
     retrieveDatasetFamilyList(instanceName: string): Observable<DatasetFamily[]> {
         return this.http.get<DatasetFamily[]>(`${this.config.apiUrl}/instance/${instanceName}/dataset-family`);
     }
 
+    /**
+     * Adds a new dataset family for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     * @param  {DatasetFamily} newDatasetFamily - The dataset family.
+     *
+     * @return Observable<DatasetFamily>
+     */
     addDatasetFamily(instanceName: string, newDatasetFamily: DatasetFamily): Observable<DatasetFamily> {
         return this.http.post<DatasetFamily>(`${this.config.apiUrl}/instance/${instanceName}/dataset-family`, newDatasetFamily);
     }
 
+    /**
+     * Modifies a dataset family.
+     *
+     * @param  {DatasetFamily} datasetFamily - The dataset family.
+     *
+     * @return Observable<DatasetFamily>
+     */
     editDatasetFamily(datasetFamily: DatasetFamily): Observable<DatasetFamily> {
         return this.http.put<DatasetFamily>(`${this.config.apiUrl}/dataset-family/${datasetFamily.id}`, datasetFamily);
     }
 
-    deleteDatasetFamily(datasetFamilyId: number) {
+    /**
+     * Removes a dataset family.
+     *
+     * @param  {number} datasetFamilyId - The dataset family ID.
+     *
+     * @return Observable<object>
+     */
+    deleteDatasetFamily(datasetFamilyId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/dataset-family/${datasetFamilyId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/dataset.service.spec.ts b/client/src/app/metamodel/services/dataset.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fce30bc1715d7c91ed2225047a0af124cfa6ce58
--- /dev/null
+++ b/client/src/app/metamodel/services/dataset.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { DatasetService } from './dataset.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Dataset } from '../models';
+import { DATASET } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] DatasetService', () => {
+    let service: DatasetService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                DatasetService
+            ]
+        });
+        service = TestBed.inject(DatasetService);
+    });
+
+    it('#retrieveDatasetList() should return an Observable<Dataset[]>',
+        inject([HttpTestingController, DatasetService],(httpMock: HttpTestingController, service: DatasetService) => {
+                const mockResponse = [];
+
+                service.retrieveDatasetList('myInstance').subscribe((event: Dataset[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/instance/myInstance/dataset');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addDataset() should return an Observable<Dataset>',
+        inject([HttpTestingController, DatasetService],(httpMock: HttpTestingController, service: DatasetService) => {
+                const mockResponse = DATASET;
+
+                service.addDataset(DATASET).subscribe((event: Dataset) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset-family/1/dataset');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editDataset() should return an Observable<Dataset>',
+        inject([HttpTestingController, DatasetService],(httpMock: HttpTestingController, service: DatasetService) => {
+                const mockResponse = DATASET;
+
+                service.editDataset(DATASET).subscribe((event: Dataset) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteDataset() should return an Observable<object>',
+        inject([HttpTestingController, DatasetService],(httpMock: HttpTestingController, service: DatasetService) => {
+                const mockResponse = {};
+
+                service.deleteDataset('myDataset').subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/dataset.service.ts b/client/src/app/metamodel/services/dataset.service.ts
index 312749da555701cecd5cdc0ff085b87fcec47f02..f82b88be174f85a4c75b638a8aebaef8995e1356 100644
--- a/client/src/app/metamodel/services/dataset.service.ts
+++ b/client/src/app/metamodel/services/dataset.service.ts
@@ -15,23 +15,55 @@ import { Observable } from 'rxjs';
 import { Dataset } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Dataset service.
+ */
 @Injectable()
 export class DatasetService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves dataset list for the given instance.
+     *
+     * @param  {string} instanceName - The instance name.
+     *
+     * @return Observable<Dataset[]>
+     */
     retrieveDatasetList(instanceName: string): Observable<Dataset[]> {
         return this.http.get<Dataset[]>(`${this.config.apiUrl}/instance/${instanceName}/dataset`);
     }
 
+    /**
+     * Adds a new dataset.
+     *
+     * @param  {Dataset} newDataset - The dataset.
+     *
+     * @return Observable<Dataset>
+     */
     addDataset(newDataset: Dataset): Observable<Dataset> {
         return this.http.post<Dataset>(`${this.config.apiUrl}/dataset-family/${newDataset.id_dataset_family}/dataset`, newDataset);
     }
 
+    /**
+     * Modifies a dataset.
+     *
+     * @param  {Dataset} dataset - The dataset.
+     *
+     * @return Observable<Dataset>
+     */
     editDataset(dataset: Dataset): Observable<Dataset> {
         return this.http.put<Dataset>(`${this.config.apiUrl}/dataset/${dataset.name}`, dataset);
     }
 
-    deleteDataset(datasetName: string) {
+    /**
+     * Removes a dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<object>
+     */
+    deleteDataset(datasetName: string): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/dataset/${datasetName}`);
     }
 }
diff --git a/client/src/app/metamodel/services/group.service.spec.ts b/client/src/app/metamodel/services/group.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5af8d3806eb3df0c0939552f3b1bc9bf73d387c
--- /dev/null
+++ b/client/src/app/metamodel/services/group.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { GroupService } from './group.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Group } from '../models';
+import { GROUP } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] GroupService', () => {
+    let service: GroupService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                GroupService
+            ]
+        });
+        service = TestBed.inject(GroupService);
+    });
+
+    it('#retrieveGroupList() should return an Observable<Group[]>',
+        inject([HttpTestingController, GroupService],(httpMock: HttpTestingController, service: GroupService) => {
+                const mockResponse = [];
+
+                service.retrieveGroupList('myInstance').subscribe((event: Group[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/instance/myInstance/group');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addGroup() should return an Observable<Group>',
+        inject([HttpTestingController, GroupService],(httpMock: HttpTestingController, service: GroupService) => {
+                const mockResponse = GROUP;
+
+                service.addGroup('myInstance', GROUP).subscribe((event: Group) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/instance/myInstance/group');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editGroup() should return an Observable<Group>',
+        inject([HttpTestingController, GroupService],(httpMock: HttpTestingController, service: GroupService) => {
+                const mockResponse = GROUP;
+
+                service.editGroup(GROUP).subscribe((event: Group) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/group/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteGroup() should return an Observable<object>',
+        inject([HttpTestingController, GroupService],(httpMock: HttpTestingController, service: GroupService) => {
+                const mockResponse = {};
+
+                service.deleteGroup(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/group/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/group.service.ts b/client/src/app/metamodel/services/group.service.ts
index a96ed07797d2d97642a3bf2d4f063e9574d4646d..a563414f0b4c247c56c6771cd8d11b9dfaf4c9b3 100644
--- a/client/src/app/metamodel/services/group.service.ts
+++ b/client/src/app/metamodel/services/group.service.ts
@@ -15,23 +15,56 @@ import { Observable } from 'rxjs';
 import { Group } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Group service.
+ */
 @Injectable()
 export class GroupService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves group list for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     *
+     * @return Observable<Group[]>
+     */
     retrieveGroupList(instanceName: string): Observable<Group[]> {
         return this.http.get<Group[]>(`${this.config.apiUrl}/instance/${instanceName}/group`);
     }
 
+    /**
+     * Adds a new group for the given instance.
+     *
+     * @param  {string} instanceName - The instance.
+     * @param  {Group} newGroup - The group.
+     *
+     * @return Observable<Group>
+     */
     addGroup(instanceName: string, newGroup: Group): Observable<Group> {
         return this.http.post<Group>(`${this.config.apiUrl}/instance/${instanceName}/group`, newGroup);
     }
 
+    /**
+     * Modifies a group.
+     *
+     * @param  {Group} group - The group.
+     *
+     * @return Observable<Group>
+     */
     editGroup(group: Group): Observable<Group> {
         return this.http.put<Group>(`${this.config.apiUrl}/group/${group.id}`, group);
     }
 
-    deleteGroup(groupId: number) {
+    /**
+     * Removes a group.
+     *
+     * @param  {number} groupId - The group ID.
+     *
+     * @return Observable<object>
+     */
+    deleteGroup(groupId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/group/${groupId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/output-category.service.spec.ts b/client/src/app/metamodel/services/output-category.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3e20ec1ad3631869f4ac51f47111bf6d53464e14
--- /dev/null
+++ b/client/src/app/metamodel/services/output-category.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { OutputCategoryService } from './output-category.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { OutputCategory, OutputFamily } from '../models';
+import { CATEGORY, OUTPUT_FAMILY } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] OutputCategoryService', () => {
+    let service: OutputCategoryService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                OutputCategoryService
+            ]
+        });
+        service = TestBed.inject(OutputCategoryService);
+    });
+
+    it('#retrieveOutputCategoryList() should return an Observable<OutputCategory[]>',
+        inject([HttpTestingController, OutputCategoryService],(httpMock: HttpTestingController, service: OutputCategoryService) => {
+                const mockResponse = [];
+
+                service.retrieveOutputCategoryList('myDataset').subscribe((event: OutputCategory[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/output-category');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addOutputCategory() should return an Observable<OutputCategory>',
+        inject([HttpTestingController, OutputCategoryService],(httpMock: HttpTestingController, service: OutputCategoryService) => {
+                const mockResponse = CATEGORY;
+
+                service.addOutputCategory(CATEGORY).subscribe((event: OutputCategory) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/output-family/1/output-category');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editOutputCategory() should return an Observable<OutputCategory>',
+        inject([HttpTestingController, OutputCategoryService],(httpMock: HttpTestingController, service: OutputCategoryService) => {
+                const mockResponse = CATEGORY;
+
+                service.editOutputCategory(CATEGORY).subscribe((event: OutputCategory) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/output-category/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteOutputCategory() should return an Observable<object>',
+        inject([HttpTestingController, OutputCategoryService],(httpMock: HttpTestingController, service: OutputCategoryService) => {
+                const mockResponse = {};
+
+                service.deleteOutputCategory(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/output-category/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/output-category.service.ts b/client/src/app/metamodel/services/output-category.service.ts
index b13ef00fdb66caecacd3a7d25c428dd263164227..ae9cc7bdff1e1daad2dcda8ff2bedc84591398e3 100644
--- a/client/src/app/metamodel/services/output-category.service.ts
+++ b/client/src/app/metamodel/services/output-category.service.ts
@@ -15,23 +15,55 @@ import { Observable } from 'rxjs';
 import { OutputCategory } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Output category service.
+ */
 @Injectable()
 export class OutputCategoryService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves output category list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<OutputCategory[]>
+     */
     retrieveOutputCategoryList(datasetName: string): Observable<OutputCategory[]> {
         return this.http.get<OutputCategory[]>(`${this.config.apiUrl}/dataset/${datasetName}/output-category`);
     }
 
+    /**
+     * Adds a new output category.
+     *
+     * @param  {OutputCategory} newOutputCategory - The output category.
+     *
+     * @return Observable<OutputCategory>
+     */
     addOutputCategory(newOutputCategory: OutputCategory): Observable<OutputCategory> {
         return this.http.post<OutputCategory>(`${this.config.apiUrl}/output-family/${newOutputCategory.id_output_family}/output-category`, newOutputCategory);
     }
 
+    /**
+     * Modifies an output category.
+     *
+     * @param  {OutputCategory} outputCategory - The output category.
+     *
+     * @return Observable<OutputCategory>
+     */
     editOutputCategory(outputCategory: OutputCategory): Observable<OutputCategory> {
         return this.http.put<OutputCategory>(`${this.config.apiUrl}/output-category/${outputCategory.id}`, outputCategory);
     }
 
-    deleteOutputCategory(outputCategoryId: number) {
+    /**
+     * Removes an output category.
+     *
+     * @param  {number} outputCategoryId - The output category ID.
+     *
+     * @return Observable<object>
+     */
+    deleteOutputCategory(outputCategoryId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/output-category/${outputCategoryId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/output-family.service.spec.ts b/client/src/app/metamodel/services/output-family.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9455a0a8f5ff10aaba49b44ba76fcc6edbe8d741
--- /dev/null
+++ b/client/src/app/metamodel/services/output-family.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { OutputFamilyService } from './output-family.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { OutputFamily } from '../models';
+import { OUTPUT_FAMILY } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] OutputFamilyService', () => {
+    let service: OutputFamilyService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                OutputFamilyService
+            ]
+        });
+        service = TestBed.inject(OutputFamilyService);
+    });
+
+    it('#retrieveOutputFamilyList() should return an Observable<OutputFamily[]>',
+        inject([HttpTestingController, OutputFamilyService],(httpMock: HttpTestingController, service: OutputFamilyService) => {
+                const mockResponse = [];
+
+                service.retrieveOutputFamilyList('myDataset').subscribe((event: OutputFamily[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/output-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addOutputFamily() should return an Observable<OutputFamily>',
+        inject([HttpTestingController, OutputFamilyService],(httpMock: HttpTestingController, service: OutputFamilyService) => {
+                const mockResponse = OUTPUT_FAMILY;
+
+                service.addOutputFamily('myDataset', OUTPUT_FAMILY).subscribe((event: OutputFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/dataset/myDataset/output-family');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editOutputFamily() should return an Observable<OutputFamily>',
+        inject([HttpTestingController, OutputFamilyService],(httpMock: HttpTestingController, service: OutputFamilyService) => {
+                const mockResponse = OUTPUT_FAMILY;
+
+                service.editOutputFamily(OUTPUT_FAMILY).subscribe((event: OutputFamily) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/output-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteOutputFamily() should return an Observable<object>',
+        inject([HttpTestingController, OutputFamilyService],(httpMock: HttpTestingController, service: OutputFamilyService) => {
+                const mockResponse = {};
+
+                service.deleteOutputFamily(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/output-family/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/output-family.service.ts b/client/src/app/metamodel/services/output-family.service.ts
index 5c73604aa1b9706f3beb798620ef69a2c95c73c8..58c05651adb19e619a8ab041a4c782677f4b1879 100644
--- a/client/src/app/metamodel/services/output-family.service.ts
+++ b/client/src/app/metamodel/services/output-family.service.ts
@@ -15,23 +15,56 @@ import { Observable } from 'rxjs';
 import { OutputFamily } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Output family service.
+ */
 @Injectable()
 export class OutputFamilyService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves output family list for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     *
+     * @return Observable<OutputFamily[]>
+     */
     retrieveOutputFamilyList(datasetName: string): Observable<OutputFamily[]> {
         return this.http.get<OutputFamily[]>(`${this.config.apiUrl}/dataset/${datasetName}/output-family`);
     }
 
+    /**
+     * Adds a new output family for the given dataset.
+     *
+     * @param  {string} datasetName - The dataset name.
+     * @param  {OutputFamily} newOutputFamily - The output family.
+     *
+     * @return Observable<OutputFamily>
+     */
     addOutputFamily(datasetName: string, newOutputFamily: OutputFamily): Observable<OutputFamily> {
         return this.http.post<OutputFamily>(`${this.config.apiUrl}/dataset/${datasetName}/output-family`, newOutputFamily);
     }
 
-    editOutputFamily(criteriaFamily: OutputFamily): Observable<OutputFamily> {
-        return this.http.put<OutputFamily>(`${this.config.apiUrl}/output-family/${criteriaFamily.id}`, criteriaFamily);
+    /**
+     * Modifies an output family.
+     *
+     * @param  {OutputFamily} outputFamily - The output family.
+     *
+     * @return Observable<OutputFamily>
+     */
+    editOutputFamily(outputFamily: OutputFamily): Observable<OutputFamily> {
+        return this.http.put<OutputFamily>(`${this.config.apiUrl}/output-family/${outputFamily.id}`, outputFamily);
     }
 
-    deleteOutputFamily(outputFamilyId: number) {
+    /**
+     * Removes an output family.
+     *
+     * @param  {number} outputFamilyId - The output family ID.
+     *
+     * @return Observable<object>
+     */
+    deleteOutputFamily(outputFamilyId: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/output-family/${outputFamilyId}`);
     }
 }
diff --git a/client/src/app/metamodel/services/root-directory.service.spec.ts b/client/src/app/metamodel/services/root-directory.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..adf40e820e48fd7c0217d4c03afece1a9df2048a
--- /dev/null
+++ b/client/src/app/metamodel/services/root-directory.service.spec.ts
@@ -0,0 +1,41 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { RootDirectoryService } from './root-directory.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { FileInfo } from '../models';
+
+describe('[Instance][Metamodel][Services] RootDirectoryService', () => {
+    let service: RootDirectoryService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                RootDirectoryService
+            ]
+        });
+        service = TestBed.inject(RootDirectoryService);
+    });
+
+    it('#retrieveRootDirectory() should return an Observable<FileInfo[]>',
+        inject([HttpTestingController, RootDirectoryService],(httpMock: HttpTestingController, service: RootDirectoryService) => {
+                const mockResponse = [];
+
+                service.retrieveRootDirectory('/path').subscribe((event: FileInfo[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/file-explorer/path');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/root-directory.service.ts b/client/src/app/metamodel/services/root-directory.service.ts
index 7935a76d4e87f55e97e5400f86e067b86dc81b47..ed8c19c37fe555106b5d84b8e0ea5041026b2056 100644
--- a/client/src/app/metamodel/services/root-directory.service.ts
+++ b/client/src/app/metamodel/services/root-directory.service.ts
@@ -15,10 +15,21 @@ import { Observable } from 'rxjs';
 import { FileInfo } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Root directory service.
+ */
 @Injectable()
 export class RootDirectoryService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves root directory with the given path.
+     *
+     * @param  {string} path - The path.
+     *
+     * @return Observable<FileInfo[]>
+     */
     retrieveRootDirectory(path: string): Observable<FileInfo[]> {
         return this.http.get<FileInfo[]>(`${this.config.apiUrl}/file-explorer${path}`);
     }
diff --git a/client/src/app/metamodel/services/select-option.service.spec.ts b/client/src/app/metamodel/services/select-option.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e079e3b0583462fb612e2917cca884937bd8df07
--- /dev/null
+++ b/client/src/app/metamodel/services/select-option.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { SelectOptionService } from './select-option.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { SelectOption } from '../models';
+import { SELECT_OPTION } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] SelectOptionService', () => {
+    let service: SelectOptionService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                SelectOptionService
+            ]
+        });
+        service = TestBed.inject(SelectOptionService);
+    });
+
+    it('#retrieveSelectOptionList() should return an Observable<SelectOption[]>',
+        inject([HttpTestingController, SelectOptionService],(httpMock: HttpTestingController, service: SelectOptionService) => {
+                const mockResponse = [];
+
+                service.retrieveSelectOptionList().subscribe((event: SelectOption[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/option');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addSelectOption() should return an Observable<SelectOption>',
+        inject([HttpTestingController, SelectOptionService],(httpMock: HttpTestingController, service: SelectOptionService) => {
+                const mockResponse = SELECT_OPTION;
+
+                service.addSelectOption(SELECT_OPTION).subscribe((event: SelectOption) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/option');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editSelectOption() should return an Observable<SelectOption>',
+        inject([HttpTestingController, SelectOptionService],(httpMock: HttpTestingController, service: SelectOptionService) => {
+                const mockResponse = SELECT_OPTION;
+
+                service.editSelectOption(SELECT_OPTION).subscribe((event: SelectOption) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/option/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteSelectOption() should return an Observable<object>',
+        inject([HttpTestingController, SelectOptionService],(httpMock: HttpTestingController, service: SelectOptionService) => {
+                const mockResponse = {};
+
+                service.deleteSelectOption(1).subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/option/1');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/select-option.service.ts b/client/src/app/metamodel/services/select-option.service.ts
index 719b902128db274dd66729fd83e8efeaa52238fb..904a627fff4c326583cb1aa7cc3356ec211999a0 100644
--- a/client/src/app/metamodel/services/select-option.service.ts
+++ b/client/src/app/metamodel/services/select-option.service.ts
@@ -15,23 +15,51 @@ import { Observable } from 'rxjs';
 import { SelectOption } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Select option service.
+ */
 @Injectable()
 export class SelectOptionService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves select option list.
+     *
+     * @return Observable<SelectOption[]>
+     */
     retrieveSelectOptionList(): Observable<SelectOption[]> {
         return this.http.get<SelectOption[]>(`${this.config.apiUrl}/option`);
     }
 
+    /**
+     * Adds a new select option.
+     *
+     * @param  {SelectOption} settingsSelectOption - The select option.
+     *
+     * @return Observable<SelectOption>
+     */
     addSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> {
         return this.http.post<SelectOption>(`${this.config.apiUrl}/option`, settingsSelectOption);
     }
 
+    /**
+     * Modifies a new select option.
+     *
+     * @param  {SelectOption} settingsSelectOption - The select option.
+     *
+     * @return Observable<SelectOption>
+     */
     editSelectOption(settingsSelectOption: SelectOption): Observable<SelectOption> {
         return this.http.put<SelectOption>(`${this.config.apiUrl}/option/${settingsSelectOption.id}`, settingsSelectOption);
     }
 
-    deleteSelectOption(id: number) {
+    /**
+     * Removes a select option.
+     *
+     * @param  {number} id - The select option ID.
+     */
+    deleteSelectOption(id: number): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/option/${id}`);
     }
 }
diff --git a/client/src/app/metamodel/services/select.service.spec.ts b/client/src/app/metamodel/services/select.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab449586c7eae1da626aedcd58503db57c4997b5
--- /dev/null
+++ b/client/src/app/metamodel/services/select.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { SelectService } from './select.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Select } from '../models';
+import { SELECT } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] SelectService', () => {
+    let service: SelectService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                SelectService
+            ]
+        });
+        service = TestBed.inject(SelectService);
+    });
+
+    it('#retrieveSelectList() should return an Observable<Select[]>',
+        inject([HttpTestingController, SelectService],(httpMock: HttpTestingController, service: SelectService) => {
+                const mockResponse = [];
+
+                service.retrieveSelectList().subscribe((event: Select[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/select');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addSelect() should return an Observable<Select>',
+        inject([HttpTestingController, SelectService],(httpMock: HttpTestingController, service: SelectService) => {
+                const mockResponse = SELECT;
+
+                service.addSelect(SELECT).subscribe((event: Select) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/select');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editSelect() should return an Observable<Select>',
+        inject([HttpTestingController, SelectService],(httpMock: HttpTestingController, service: SelectService) => {
+                const mockResponse = SELECT;
+
+                service.editSelect(SELECT).subscribe((event: Select) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/select/mySelect');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteSelect() should return an Observable<object>',
+        inject([HttpTestingController, SelectService],(httpMock: HttpTestingController, service: SelectService) => {
+                const mockResponse = {};
+
+                service.deleteSelect('mySelect').subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/select/mySelect');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/select.service.ts b/client/src/app/metamodel/services/select.service.ts
index f1e440f8f6c87cfd6acb41c370b2407d3ecbac4e..337ed1b2bd66f581ed85987f11b373c09cf0a641 100644
--- a/client/src/app/metamodel/services/select.service.ts
+++ b/client/src/app/metamodel/services/select.service.ts
@@ -15,23 +15,53 @@ import { Observable } from 'rxjs';
 import { Select } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Select service.
+ */
 @Injectable()
 export class SelectService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves select list.
+     *
+     * @return Observable<Select[]>
+     */
     retrieveSelectList(): Observable<Select[]> {
         return this.http.get<Select[]>(`${this.config.apiUrl}/select`);
     }
 
+    /**
+     * Adds a new select.
+     *
+     * @param  {Select} select - The select.
+     *
+     * @return Observable<Select>
+     */
     addSelect(select: Select): Observable<Select> {
         return this.http.post<Select>(`${this.config.apiUrl}/select`, select);
     }
 
+    /**
+     * Modifies a select.
+     *
+     * @param  {Select} select - The select.
+     *
+     * @return Observable<Select>
+     */
     editSelect(select: Select): Observable<Select> {
         return this.http.put<Select>(`${this.config.apiUrl}/select/${select.name}`, select);
     }
 
-    deleteSelect(name: string) {
+    /**
+     * Removes a select.
+     *
+     * @param  {string} name - The select name.
+     *
+     * @return Observable<object>
+     */
+    deleteSelect(name: string): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/select/${name}`);
     }
 }
diff --git a/client/src/app/metamodel/services/survey.service.spec.ts b/client/src/app/metamodel/services/survey.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de82c5e0db5b8ba39a51a53ac7609b2a6eb479cf
--- /dev/null
+++ b/client/src/app/metamodel/services/survey.service.spec.ts
@@ -0,0 +1,102 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { SurveyService } from './survey.service';
+import { AppConfigService } from 'src/app/app-config.service';
+import { Survey } from '../models';
+import { SURVEY } from '../../../test-data';
+
+describe('[Instance][Metamodel][Services] SurveyService', () => {
+    let service: SurveyService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                SurveyService
+            ]
+        });
+        service = TestBed.inject(SurveyService);
+    });
+
+    it('#retrieveSurveyList() should return an Observable<Survey[]>',
+        inject([HttpTestingController, SurveyService],(httpMock: HttpTestingController, service: SurveyService) => {
+                const mockResponse = [];
+
+                service.retrieveSurveyList().subscribe((event: Survey[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/survey');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('GET');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#addSurvey() should return an Observable<Survey>',
+        inject([HttpTestingController, SurveyService],(httpMock: HttpTestingController, service: SurveyService) => {
+                const mockResponse = SURVEY;
+
+                service.addSurvey(SURVEY).subscribe((event: Survey) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/survey');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('POST');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#editSurvey() should return an Observable<Survey>',
+        inject([HttpTestingController, SurveyService],(httpMock: HttpTestingController, service: SurveyService) => {
+                const mockResponse = SURVEY;
+
+                service.editSurvey(SURVEY).subscribe((event: Survey) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/survey/mySurvey');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('PUT');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+
+    it('#deleteSurvey() should return an Observable<object>',
+        inject([HttpTestingController, SurveyService],(httpMock: HttpTestingController, service: SurveyService) => {
+                const mockResponse = {};
+
+                service.deleteSurvey('mySurvey').subscribe((event: object) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/survey/mySurvey');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.method).toEqual('DELETE');
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/survey.service.ts b/client/src/app/metamodel/services/survey.service.ts
index d4fdd6e3d294f76786a64dd9a3be5e01e6153131..926e6618528e1bca3a9a2242f68a9bbecaf2cc45 100644
--- a/client/src/app/metamodel/services/survey.service.ts
+++ b/client/src/app/metamodel/services/survey.service.ts
@@ -15,23 +15,53 @@ import { Observable } from 'rxjs';
 import { Survey } from '../models';
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Survey service.
+ */
 @Injectable()
 export class SurveyService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves survey list.
+     *
+     * @return Observable<Survey[]>
+     */
     retrieveSurveyList(): Observable<Survey[]> {
         return this.http.get<Survey[]>(`${this.config.apiUrl}/survey`);
     }
 
+    /**
+     * Adds a new survey.
+     *
+     * @param  {Survey} newSurvey - The survey.
+     *
+     * @return Observable<Survey>
+     */
     addSurvey(newSurvey: Survey): Observable<Survey> {
         return this.http.post<Survey>(`${this.config.apiUrl}/survey`, newSurvey);
     }
 
+    /**
+     * Modifies a survey.
+     *
+     * @param  {Survey} survey - The survey.
+     *
+     * @return Observable<Survey>
+     */
     editSurvey(survey: Survey): Observable<Survey> {
         return this.http.put<Survey>(`${this.config.apiUrl}/survey/${survey.name}`, survey);
     }
 
-    deleteSurvey(surveyName: string) {
+    /**
+     * Removes a survey.
+     *
+     * @param  {string} surveyName - The survey name.
+     *
+     * @return Observable<object>
+     */
+    deleteSurvey(surveyName: string): Observable<object> {
         return this.http.delete(`${this.config.apiUrl}/survey/${surveyName}`);
     }
 }
diff --git a/client/src/app/metamodel/services/table.service.spec.ts b/client/src/app/metamodel/services/table.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..525cf0841d73a6db25c05cb0a7333b6a3bd80848
--- /dev/null
+++ b/client/src/app/metamodel/services/table.service.spec.ts
@@ -0,0 +1,39 @@
+import { TestBed, inject } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { TableService } from './table.service';
+import { AppConfigService } from 'src/app/app-config.service';
+
+describe('[Instance][Metamodel][Services] TableService', () => {
+    let service: TableService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [HttpClientTestingModule],
+            providers: [
+                { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } },
+                TableService
+            ]
+        });
+        service = TestBed.inject(TableService);
+    });
+
+    it('#retrieveTableList() should return an Observable<string[]>',
+        inject([HttpTestingController, TableService],(httpMock: HttpTestingController, service: TableService) => {
+                const mockResponse = ['Table'];
+
+                service.retrieveTableList(1).subscribe((event: string[]) => {
+                    expect(event).toEqual(mockResponse);
+                });
+
+                const mockRequest = httpMock.expectOne('http://testing.com/database/1/table');
+
+                expect(mockRequest.cancelled).toBeFalsy();
+                expect(mockRequest.request.responseType).toEqual('json');
+                mockRequest.flush(mockResponse);
+
+                httpMock.verify();
+            }
+        )
+    );
+});
diff --git a/client/src/app/metamodel/services/table.service.ts b/client/src/app/metamodel/services/table.service.ts
index 2d67e4a1860d21975117bcbcdb99f52401925e4a..c9fe534dca73eeaf4872c377d6615a13062c2c9f 100644
--- a/client/src/app/metamodel/services/table.service.ts
+++ b/client/src/app/metamodel/services/table.service.ts
@@ -14,10 +14,21 @@ import { Observable } from 'rxjs';
 
 import { AppConfigService } from 'src/app/app-config.service';
 
+/**
+ * @class
+ * @classdesc Table service.
+ */
 @Injectable()
 export class TableService {
     constructor(private http: HttpClient, private config: AppConfigService) { }
 
+    /**
+     * Retrieves results for the given parameters.
+     *
+     * @param  {number} idDatabase - The database ID.
+     *
+     * @return Observable<string[]>
+     */
     retrieveTableList(idDatabase: number): Observable<string[]> {
         return this.http.get<string[]>(`${this.config.apiUrl}/database/${idDatabase}/table`);
     }
diff --git a/client/src/test-data.ts b/client/src/test-data.ts
index 69575072bb5b538deaf44ebc1f551865421e38e0..fbde4ef15bbaa951120e5135cb4f1561f9263573 100644
--- a/client/src/test-data.ts
+++ b/client/src/test-data.ts
@@ -1,13 +1,47 @@
 import {
-    Attribute,
+    Attribute, Column, CriteriaFamily, Database,
     Dataset,
-    DatasetFamily,
+    DatasetFamily, FileInfo, Group,
     Instance,
     OutputCategory,
-    OutputFamily, SelectOption,
+    OutputFamily, Select, SelectOption,
     Survey
 } from './app/metamodel/models';
 
+export const DATABASE_LIST: Database[] = [
+    {
+        id: 1,
+        label: 'database one',
+        dbname: 'database-one',
+        dbtype: 'type',
+        dbhost: 'host',
+        dbport: 1234,
+        dblogin: 'login',
+        dbpassword: 'pwd'
+    },
+    {
+        id: 2,
+        label: 'database two',
+        dbname: 'database-two',
+        dbtype: 'type',
+        dbhost: 'host',
+        dbport: 1234,
+        dblogin: 'login',
+        dbpassword: 'pwd'
+    }
+];
+
+export const DATABASE: Database = {
+    id: 1,
+    label: 'my database',
+    dbname: 'myDatabase',
+    dbtype: 'type',
+    dbhost: 'host',
+    dbport: 1234,
+    dblogin: 'login',
+    dbpassword: 'pwd'
+};
+
 export const SURVEY_LIST: Survey[] = [
     {
         name: 'survey-one',
@@ -29,6 +63,16 @@ export const SURVEY_LIST: Survey[] = [
     }
 ];
 
+export const SURVEY: Survey = {
+    name: 'mySurvey',
+    label: 'My Survey',
+    description: 'This is my survey',
+    link: 'http://my-survey.com',
+    manager: 'Orelsan',
+    id_database: 1,
+    nb_datasets: 2
+};
+
 export const INSTANCE_LIST: Instance[] = [
     {
         name: 'myOtherInstance',
@@ -132,11 +176,35 @@ export const INSTANCE: Instance = {
     nb_datasets: 2
 };
 
+export const GROUP_LIST: Group[] = [
+    {
+        id: 1,
+        role: 'admin',
+        instance_name: 'myInstance',
+        datasets: ['myDataset', 'otherDataset']
+    },
+    {
+        id: 2,
+        role: 'guest',
+        instance_name: 'myInstance',
+        datasets: ['myDataset', 'otherDataset']
+    }
+];
+
+export const GROUP: Group = {
+    id: 1,
+    role: 'admin',
+    instance_name: 'myInstance',
+    datasets: ['myDataset', 'otherDataset']
+};
+
 export const DATASET_FAMILY_LIST: DatasetFamily[] = [
     { id: 2, label: 'My second dataset family', display: 2, opened: false },
     { id: 1, label: 'My first dataset family', display: 1, opened: true }
 ];
 
+export const DATASET_FAMILY: DatasetFamily = { id: 1, label: 'My first dataset family', display: 1, opened: true };
+
 export const DATASET_LIST: Dataset[] = [
     {
         name: 'myDataset',
@@ -210,7 +278,7 @@ export const DATASET_LIST: Dataset[] = [
                 survey_label: 'More about this survey'
             },
             cone_search: {
-                cone_search_enabled: true,
+                cone_search_enabled: false,
                 cone_search_opened: true,
                 cone_search_column_ra: 1,
                 cone_search_column_dec: 2,
@@ -389,6 +457,36 @@ export const ATTRIBUTE_LIST: Attribute[] = [
     }
 ];
 
+export const ATTRIBUTE: Attribute = {
+    id: 1,
+    name: 'name_one',
+    label: 'label_one',
+    form_label: 'form_label_one',
+    description: 'description_one',
+    output_display: 2,
+    criteria_display: 2,
+    search_flag: 'ID',
+    search_type: 'field',
+    operator: '=',
+    type: 'integer',
+    order_by: true,
+    detail: true,
+    display_detail: 2,
+    options: [
+        { label: 'Three', value: 'three', display: 3 },
+        { label: 'One', value: 'one', display: 1 },
+        { label: 'Two', value: 'two', display: 2 }
+    ],
+    id_output_category: 2
+};
+
+export const COLUMN_LIST: Column[] = [
+    { name: 'myCol', type: 'type' },
+    { name: 'anotherCol', type: 'type' }
+];
+
+export const COLUMN: Column = { name: 'myCol', type: 'type' };
+
 export const CATEGORY_LIST: OutputCategory[] = [
     {
         id: 1,
@@ -410,17 +508,68 @@ export const CATEGORY_LIST: OutputCategory[] = [
     }
 ];
 
+export const CATEGORY: OutputCategory = {
+    id: 1,
+    label: 'Another output category',
+    display: 20,
+    id_output_family: 1
+};
+
 export const OUTPUT_FAMILY_LIST: OutputFamily[] = [
     { id: 2, label: 'Output family Two', display: 2, opened: true },
     { id: 1, label: 'Output family One', display: 1, opened: false }
 ];
 
+export const OUTPUT_FAMILY: OutputFamily = { id: 1, label: 'Output family One', display: 1, opened: false };
+
+export const CRITERIA_FAMILY_LIST: CriteriaFamily[] = [
+    {
+        id: 1,
+        label: 'myCriteriaFamily',
+        display: 1,
+        opened: true
+    },
+    {
+        id: 2,
+        label: 'anotherCriteriaFamily',
+        display: 1,
+        opened: true
+    }
+];
+
+export const CRITERIA_FAMILY: CriteriaFamily = {
+    id: 1,
+    label: 'myCriteriaFamily',
+    display: 1,
+    opened: true
+};
+
+export const SELECT_LIST: Select[] = [
+    { name: 'select-one', label: 'Select one' },
+    { name: 'select-two', label: 'Select two' }
+];
+
+export const SELECT: Select = { name: 'mySelect', label: 'My select' };
+
 export const SELECT_OPTION_LIST: SelectOption[] = [
     { id: 2, label: 'select_option_label_two', value: 'select_option_label_two', display: 2, select_name: 'name_two' },
     { id: 1, label: 'select_option_label_one', value: 'select_option_label_one', display: 1, select_name: 'name_one' }
 ];
 
+export const SELECT_OPTION: SelectOption = {
+    id: 1,
+    label: 'select_option_label_one',
+    value: 'select_option_label_one',
+    display: 1,
+    select_name: 'name_one'
+};
+
 export const OBJECT_DETAIL: any = {
     label_four: 'spec1d',
     label_five: 5
 };
+
+export const FILES: FileInfo[] = [
+    { name: 'file-one', size: 1, type: 'type', mimetype: 'mimetype' },
+    { name: 'file-two', size: 2, type: 'type', mimetype: 'mimetype' },
+];