diff --git a/client/src/app/instance/instance-routing.module.ts b/client/src/app/instance/instance-routing.module.ts
index 53ae1296d4a2648f295956697e42626368d31be5..e6795b8b3773c388b49be1bf6f66322564b78b99 100644
--- a/client/src/app/instance/instance-routing.module.ts
+++ b/client/src/app/instance/instance-routing.module.ts
@@ -24,6 +24,10 @@ const routes: Routes = [
     }
 ];
 
+/**
+ * @class
+ * @classdesc Instance routing module.
+ */
 @NgModule({
     imports: [RouterModule.forChild(routes)],
     exports: [RouterModule]
diff --git a/client/src/app/instance/instance.component.spec.ts b/client/src/app/instance/instance.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..06d51a4436643232c7850fe75179743c1ac2c1b8
--- /dev/null
+++ b/client/src/app/instance/instance.component.spec.ts
@@ -0,0 +1,155 @@
+import { Component, Input } from '@angular/core';
+import { TestBed, waitForAsync, ComponentFixture  } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { provideMockStore, MockStore } from '@ngrx/store/testing';
+import { of } from 'rxjs';
+
+import { InstanceComponent } from './instance.component';
+import { AppConfigService } from 'src/app/app-config.service';
+import * as authActions from 'src/app/auth/auth.actions';
+import { Instance } from '../metamodel/models';
+import { UserProfile } from '../auth/user-profile.model';
+import * as datasetFamilyActions from '../metamodel/actions/dataset-family.actions';
+import * as datasetActions from '../metamodel/actions/dataset.actions';
+import * as surveyActions from '../metamodel/actions/survey.actions';
+
+describe('InstanceComponent', () => {
+    @Component({ selector: 'app-navbar', template: '' })
+    class NavbarStubComponent {
+        @Input() links: {label: string, icon: string, routerLink: string}[];
+        @Input() isAuthenticated: boolean;
+        @Input() userProfile: UserProfile = null;
+        @Input() baseHref: string;
+        @Input() authenticationEnabled: boolean;
+        @Input() apiUrl: string;
+        @Input() instance: Instance;
+    }
+
+    let component: InstanceComponent;
+    let fixture: ComponentFixture<InstanceComponent>;
+    let store: MockStore;
+    let appConfigServiceStub = new AppConfigService();
+
+    beforeEach(waitForAsync(() => {
+        TestBed.configureTestingModule({
+            imports: [RouterTestingModule],
+            declarations: [
+                InstanceComponent,
+                NavbarStubComponent
+            ],
+            providers: [
+                provideMockStore({ }),
+                { provide: AppConfigService, useValue: appConfigServiceStub }
+            ]
+        }).compileComponents();
+        fixture = TestBed.createComponent(InstanceComponent);
+        component = fixture.componentInstance;
+        store = TestBed.inject(MockStore);
+        document.body.innerHTML =
+            '<title id="title">Default title</title>' +
+            '<link id="favicon" href="">';
+    }));
+
+    it('should create the component', () => {
+        expect(component).toBeDefined();
+    });
+
+    it('should execute ngOnInit lifecycle', (done) => {
+        const instance: Instance = {
+            name: 'myInstance',
+            label: 'My Instance',
+            data_path: 'data/path',
+            config: {
+                design: {
+                    design_color: 'green',
+                    design_background_color: 'darker green',
+                    design_logo: 'path/to/logo',
+                    design_favicon: 'path/to/favicon'
+                },
+                home: {
+                    home_component: 'HomeComponent',
+                    home_config: {
+                        home_component_text: 'Description',
+                        home_component_logo: 'path/to/logo'
+                    }
+                },
+                search: {
+                    search_by_criteria_allowed: true,
+                    search_by_criteria_label: 'Search',
+                    search_multiple_allowed: true,
+                    search_multiple_label: 'Search multiple',
+                    search_multiple_all_datasets_selected: false
+                },
+                documentation: {
+                    documentation_allowed: true,
+                    documentation_label: 'Documentation'
+                }
+            },
+            nb_dataset_families: 1,
+            nb_datasets: 2
+        };
+        component.instance = of(instance);
+        const spy = jest.spyOn(store, 'dispatch');
+        const expectedLinks = [
+            { label: 'Home', icon: 'fas fa-home', routerLink: 'home' },
+            { label: 'Search', icon: 'fas fa-search', routerLink: 'search' },
+            { label: 'Search multiple', icon: 'fas fa-search-plus', routerLink: 'search-multiple' },
+            { label: 'Documentation', icon: 'fas fa-question', routerLink: 'documentation' }
+        ];
+        component.ngOnInit();
+        Promise.resolve(null).then(function() {
+            expect(spy).toHaveBeenCalledTimes(3);
+            expect(spy).toHaveBeenCalledWith(datasetFamilyActions.loadDatasetFamilyList());
+            expect(spy).toHaveBeenCalledWith(datasetActions.loadDatasetList());
+            expect(spy).toHaveBeenCalledWith(surveyActions.loadSurveyList());
+            expect(component.links).toEqual(expectedLinks);
+            expect(component.favIcon.href).toEqual('http://localhost/undefined/download-instance-file/myInstance/path/to/favicon');
+            expect(component.title.textContent).toEqual('My Instance');
+            done();
+        });
+    });
+
+    it('#getBaseHref() should return base href config key value', () => {
+        appConfigServiceStub.baseHref = '/my-project';
+        expect(component.getBaseHref()).toBe('/my-project');
+    });
+
+    it('#authenticationEnabled() should return authentication enabled config key value', () => {
+        appConfigServiceStub.authenticationEnabled = true;
+        expect(component.getAuthenticationEnabled()).toBeTruthy();
+    });
+
+    it('#getApiUrl() should return API URL', () => {
+        appConfigServiceStub.apiUrl = 'http:test.com';
+        expect(component.getApiUrl()).toEqual('http:test.com');
+    });
+
+    it('#login() should dispatch login action', () => {
+        const spy = jest.spyOn(store, 'dispatch');
+        component.login();
+        expect(spy).toHaveBeenCalledTimes(1);
+        expect(spy).toHaveBeenCalledWith(authActions.login());
+    });
+
+    it('#logout() should dispatch logout action', () => {
+        const spy = jest.spyOn(store, 'dispatch');
+        component.logout();
+        expect(spy).toHaveBeenCalledTimes(1);
+        expect(spy).toHaveBeenCalledWith(authActions.logout());
+    });
+
+    it('#openEditProfile() should dispatch open edit profile action', () => {
+        const spy = jest.spyOn(store, 'dispatch');
+        component.openEditProfile();
+        expect(spy).toHaveBeenCalledTimes(1);
+        expect(spy).toHaveBeenCalledWith(authActions.openEditProfile());
+    });
+
+    it('should unsubscribe to instance when component is destroyed', () => {
+        component.instanceSubscription = of().subscribe();
+        const spy = jest.spyOn(component.instanceSubscription, 'unsubscribe');
+        component.ngOnDestroy();
+        expect(spy).toHaveBeenCalledTimes(1);
+    });
+});
diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts
index fa981f4b2c09c1d62b2d27f7ee1c1917a6ca8db7..bb78d58ce124b33a795c7e5f88dc0fee2a80bc7a 100644
--- a/client/src/app/instance/instance.component.ts
+++ b/client/src/app/instance/instance.component.ts
@@ -8,8 +8,9 @@
  */
 
 import { Component, OnDestroy, OnInit } from '@angular/core';
-import { Observable, Subscription } from 'rxjs';
+
 import { Store } from '@ngrx/store';
+import { Observable, Subscription } from 'rxjs';
 
 import { UserProfile } from 'src/app/auth/user-profile.model';
 import { Instance } from 'src/app/metamodel/models';
@@ -21,25 +22,25 @@ import * as surveyActions from 'src/app/metamodel/actions/survey.actions';
 import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector';
 import { AppConfigService } from 'src/app/app-config.service';
 
-@Component({
-    selector: 'app-instance',
-    templateUrl: 'instance.component.html'
-})
 /**
  * @class
  * @classdesc Instance container
+ *
+ * @implements OnInit
+ * @implements OnDestroy
  */
+@Component({
+    selector: 'app-instance',
+    templateUrl: 'instance.component.html'
+})
 export class InstanceComponent implements OnInit, OnDestroy {
     public favIcon: HTMLLinkElement = document.querySelector('#favicon');
     public title: HTMLLinkElement = document.querySelector('#title');
-    public links = [
-        { label: 'Home', icon: 'fas fa-home', routerLink: 'home' }
-    ];
+    public links = [{ label: 'Home', icon: 'fas fa-home', routerLink: 'home' }];
     public instance: Observable<Instance>;
     public isAuthenticated: Observable<boolean>;
     public userProfile: Observable<UserProfile>;
     public userRoles: Observable<string[]>;
-
     public instanceSubscription: Subscription;
 
     constructor(private store: Store<{ }>, private config: AppConfigService) {
@@ -50,6 +51,8 @@ export class InstanceComponent implements OnInit, OnDestroy {
     }
 
     ngOnInit() {
+        // Create a micro task that is processed after the current synchronous code
+        // This micro task prevent the expression has changed after view init error
         Promise.resolve(null).then(() => this.store.dispatch(datasetFamilyActions.loadDatasetFamilyList()));
         Promise.resolve(null).then(() => this.store.dispatch(datasetActions.loadDatasetList()));
         Promise.resolve(null).then(() => this.store.dispatch(surveyActions.loadSurveyList()));
@@ -70,31 +73,58 @@ export class InstanceComponent implements OnInit, OnDestroy {
         })
     }
 
-    getBaseHref() {
+    /**
+     * Returns application base href.
+     *
+     * @return string
+     */
+    getBaseHref(): string {
         return this.config.baseHref;
     }
 
-    getAuthenticationEnabled() {
+    /**
+     * Checks if authentication is enabled.
+     *
+     * @return boolean
+     */
+    getAuthenticationEnabled(): boolean {
         return this.config.authenticationEnabled;
     }
 
-    getApiUrl() {
+    /**
+     * Returns API URL.
+     *
+     * @return string
+     */
+    getApiUrl(): string {
         return this.config.apiUrl;
     }
 
+    /**
+     * Dispatches action to log in.
+     */
     login(): void {
         this.store.dispatch(authActions.login());
     }
 
+    /**
+     * Dispatches action to log out.
+     */
     logout(): void {
         this.store.dispatch(authActions.logout());
     }
 
+    /**
+     * Dispatches action to open profile editor.
+     */
     openEditProfile(): void {
         this.store.dispatch(authActions.openEditProfile());
     }
 
+    /**
+     * Unsubscribes to instance when component is destroyed.
+     */
     ngOnDestroy() {
-        this.instanceSubscription.unsubscribe();
+        if (this.instanceSubscription) this.instanceSubscription.unsubscribe();
     }
 }
diff --git a/client/src/app/instance/instance.module.ts b/client/src/app/instance/instance.module.ts
index ffedd2ae3a7e9336104f44a5f56aa14b9d54c56b..33a6514300c63f72977df896246e0cf0bc1135a9 100644
--- a/client/src/app/instance/instance.module.ts
+++ b/client/src/app/instance/instance.module.ts
@@ -18,6 +18,10 @@ import { instanceReducer } from './instance.reducer';
 import { instanceEffects } from './store/effects';
 import { instanceServices } from './store/services';
 
+/**
+ * @class
+ * @classdesc Instance module.
+ */
 @NgModule({
     imports: [
         SharedModule,
diff --git a/client/src/app/instance/instance.reducer.ts b/client/src/app/instance/instance.reducer.ts
index d14c08ae52efefef8b043088ae0b74cddbb1a44d..2c3b3c970e64e3c185933ae2cd8f57515fe97bd8 100644
--- a/client/src/app/instance/instance.reducer.ts
+++ b/client/src/app/instance/instance.reducer.ts
@@ -16,6 +16,11 @@ import * as samp from './store/reducers/samp.reducer';
 import * as coneSearch from './store/reducers/cone-search.reducer';
 import * as detail from './store/reducers/detail.reducer';
 
+/**
+ * Interface for instance state.
+ *
+ * @interface State
+ */
 export interface State {
     search: search.State,
     searchMultiple: searchMultiple.State,
diff --git a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts
index ed415dad1fe4868f45a89b6451b1e40a623f3d86..c3aaca6262d8b64615159ad9ac970bca5190bbec 100644
--- a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts
+++ b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts
@@ -9,20 +9,13 @@ import { ToastrService } from 'ngx-toastr';
 
 import { SearchMultipleEffects } from './search-multiple.effects';
 import { SearchService } from '../services/search.service';
-import * as searchActions from '../actions/search.actions';
 import * as fromSearch from '../reducers/search.reducer';
 import * as fromSearchMultiple from '../reducers/search-multiple.reducer';
 import * as fromInstance from '../../../metamodel/reducers/instance.reducer';
 import * as datasetSelector from '../../../metamodel/selectors/dataset.selector';
-import * as searchSelector from '../selectors/search.selector';
-import * as attributeActions from '../../../metamodel/actions/attribute.actions';
-import * as criteriaFamilyActions from '../../../metamodel/actions/criteria-family.actions';
-import * as outputFamilyActions from '../../../metamodel/actions/output-family.actions';
-import * as outputCategoryActions from '../../../metamodel/actions/output-category.actions';
-import * as attributeSelector from '../../../metamodel/selectors/attribute.selector';
 import * as coneSearchSelector from '../selectors/cone-search.selector';
 import * as coneSearchActions from '../actions/cone-search.actions';
-import { ConeSearch, Criterion, PaginationOrder, SearchMultipleDatasetLength } from '../models';
+import { ConeSearch, SearchMultipleDatasetLength } from '../models';
 import * as searchMultipleSelector from '../selectors/search-multiple.selector';
 import * as instanceSelector from '../../../metamodel/selectors/instance.selector';
 import * as searchMultipleActions from '../actions/search-multiple.actions';
@@ -322,67 +315,6 @@ describe('SearchMultipleEffects', () => {
 
             expect(effects.initSearch$).toBeObservable(expected);
         });
-
-    //     it('should dispatch a bunch of actions when a dataset is selected or a page is reloaded with steps checked', () => {
-    //         mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
-    //             datasetSelector.selectDatasetNameByRoute, 'myDatasetName'
-    //         );
-    //         mockSearchSelectorSelectPristine = store.overrideSelector(
-    //             searchSelector.selectPristine,true
-    //         );
-    //         mockSearchSelectorSelectCurrentDataset = store.overrideSelector(
-    //             searchSelector.selectCurrentDataset,'myDatasetName'
-    //         );
-    //         mockSearchSelectorSelectStepsByRoute = store.overrideSelector(
-    //             searchSelector.selectStepsByRoute, '111'
-    //         );
-    //
-    //         const action = searchActions.initSearch();
-    //         actions = hot('-a', { a: action });
-    //         const expected = cold('-(bcdefghi)', {
-    //             b: searchActions.changeCurrentDataset({ currentDataset: 'myDatasetName' }),
-    //             c: attributeActions.loadAttributeList(),
-    //             d: criteriaFamilyActions.loadCriteriaFamilyList(),
-    //             e: outputFamilyActions.loadOutputFamilyList(),
-    //             f: outputCategoryActions.loadOutputCategoryList(),
-    //             g: searchActions.checkCriteria(),
-    //             h: searchActions.checkOutput(),
-    //             i: searchActions.checkResult()
-    //         });
-    //
-    //         expect(effects.initSearch$).toBeObservable(expected);
-    //     });
-    //
-    //     it('should dispatch a resetSearch action when user get back to search module', () => {
-    //         mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
-    //             datasetSelector.selectDatasetNameByRoute, ''
-    //         );
-    //         mockSearchSelectorSelectPristine = store.overrideSelector(
-    //             searchSelector.selectPristine,false
-    //         );
-    //
-    //         const action = searchActions.initSearch();
-    //         const outcome = searchActions.resetSearch();
-    //
-    //         actions = hot('-a', { a: action });
-    //         const expected = cold('-b', { b: outcome });
-    //
-    //         expect(effects.initSearch$).toBeObservable(expected);
-    //     });
-    //
-    //     it('should not dispatch action when step changed on same search', () => {
-    //         mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
-    //             datasetSelector.selectDatasetNameByRoute, ''
-    //         );
-    //
-    //         const action = searchActions.initSearch();
-    //         const outcome = { type: '[No Action] Init Search' };
-    //
-    //         actions = hot('-a', { a: action });
-    //         const expected = cold('-b', { b: outcome });
-    //
-    //         expect(effects.initSearch$).toBeObservable(expected);
-    //     });
     });
 
     describe('restartSearch$ effect', () => {
@@ -487,86 +419,4 @@ describe('SearchMultipleEffects', () => {
             expect(spy).toHaveBeenCalledWith('Loading Failed', 'The search multiple data length loading failed');
         });
     });
-
-    // describe('retrieveData$ effect', () => {
-    //     it('should dispatch the retrieveDataSuccess action on success', () => {
-    //         const action = searchActions.retrieveData( {
-    //             pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a }
-    //         });
-    //         const outcome = searchActions.retrieveDataSuccess({ data: ['data'] });
-    //
-    //         actions = hot('-a', { a: action });
-    //         const response = cold('-b|', { b: ['data'] });
-    //         const expected = cold('--c', { c: outcome });
-    //         searchService.retrieveData = jest.fn(() => response);
-    //
-    //         expect(effects.retrieveData$).toBeObservable(expected);
-    //     });
-    //
-    //     it('should dispatch the retrieveDataFail action on failure', () => {
-    //         const action = searchActions.retrieveData({
-    //             pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a }
-    //         });
-    //         const error = new Error();
-    //         const outcome = searchActions.retrieveDataFail();
-    //
-    //         actions = hot('-a', { a: action });
-    //         const response = cold('-#|', {}, error);
-    //         const expected = cold('--b', { b: outcome });
-    //         searchService.retrieveData = jest.fn(() => response);
-    //
-    //         expect(effects.retrieveData$).toBeObservable(expected);
-    //     });
-    //
-    //     it('should pass correct query to the service', () => {
-    //         mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector(
-    //             datasetSelector.selectDatasetNameByRoute, 'myDataset'
-    //         );
-    //         mockSearchSelectorSelectCriteriaList = store.overrideSelector(
-    //             searchSelector.selectCriteriaList, [{'id':1,'type':'field','operator':'eq','value':'one'} as Criterion]
-    //         );
-    //         mockConeSearchSelectorSelectConeSearch = store.overrideSelector(
-    //             coneSearchSelector.selectConeSearch, { ra: 1, dec: 2, radius: 3 }
-    //         );
-    //         mockSearchSelectorSelectOutputList = store.overrideSelector(
-    //             searchSelector.selectOutputList, [1, 2]
-    //         );
-    //
-    //         jest.spyOn(searchService, 'retrieveData');
-    //
-    //         const action = searchActions.retrieveData({
-    //             pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a }
-    //         });
-    //         const outcome = searchActions.retrieveDataSuccess({ data: ['data'] });
-    //
-    //         actions = hot('-a', { a: action });
-    //         const response = cold('-b|', { b: ['data'] });
-    //         const expected = cold('--c', { c: outcome });
-    //         searchService.retrieveData = jest.fn(() => response);
-    //
-    //         expect(effects.retrieveData$).toBeObservable(expected);
-    //         expect(searchService.retrieveData).toHaveBeenCalledTimes(1);
-    //         expect(searchService.retrieveData).toHaveBeenCalledWith('myDataset?a=1;2&c=1::eq::one&cs=1:2:3&p=10:1&o=1:a');
-    //     });
-    // });
-
-    // describe('retrieveDataFail$ effect', () => {
-    //     it('should not dispatch', () => {
-    //         expect(metadata.retrieveDataFail$).toEqual(
-    //             expect.objectContaining({ dispatch: false })
-    //         );
-    //     });
-    //
-    //     it('should display a error notification', () => {
-    //         const spy = jest.spyOn(toastr, 'error');
-    //         const action = searchActions.retrieveDataFail();
-    //
-    //         actions = hot('a', { a: action });
-    //         const expected = cold('a', { a: action });
-    //
-    //         expect(effects.retrieveDataFail$).toBeObservable(expected);
-    //         expect(spy).toHaveBeenCalledTimes(1);
-    //         expect(spy).toHaveBeenCalledWith('Loading Failed', 'The search data loading failed');
-    //     });
-    // });
 });