diff --git a/src/models/fixtures/pluginFixture.ts b/src/models/fixtures/pluginFixture.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e1d83f8fe79ef99b8f6326fb1f392387752e884
--- /dev/null
+++ b/src/models/fixtures/pluginFixture.ts
@@ -0,0 +1,8 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { pluginSchema } from '../pluginSchema';
+
+export const pluginFixture = createFixture(pluginSchema, {
+  seed: ZOD_SEED,
+});
diff --git a/src/models/pluginSchema.ts b/src/models/pluginSchema.ts
index 0204b6f05d9e797c3cbad124eb063222bcc3e6f3..2cfb0725fc31a652b5db6d5e38996002eb262af9 100644
--- a/src/models/pluginSchema.ts
+++ b/src/models/pluginSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-magic-numbers */
 import { z } from 'zod';
 
 export const pluginSchema = z.object({
@@ -6,5 +7,5 @@ export const pluginSchema = z.object({
   version: z.string(),
   isPublic: z.boolean(),
   isDefault: z.boolean(),
-  urls: z.array(z.string()),
+  urls: z.array(z.string().min(1)),
 });
diff --git a/src/redux/plugins/overlays.mock.ts b/src/redux/plugins/plugins.mock.ts
similarity index 100%
rename from src/redux/plugins/overlays.mock.ts
rename to src/redux/plugins/plugins.mock.ts
diff --git a/src/redux/plugins/plugins.reducers.test.ts b/src/redux/plugins/plugins.reducers.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..faf47803f3f3537557e86386a69b441ed2e7a3d2
--- /dev/null
+++ b/src/redux/plugins/plugins.reducers.test.ts
@@ -0,0 +1,102 @@
+/* eslint-disable no-magic-numbers */
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { pluginFixture } from '@/models/fixtures/pluginFixture';
+import { apiPath } from '../apiPath';
+import { PluginsState } from './plugins.types';
+import pluginsReducer, { removePlugin } from './plugins.slice';
+import { registerPlugin } from './plugins.thunk';
+
+const mockedAxiosClient = mockNetworkResponse();
+
+const INITIAL_STATE: PluginsState = {
+  data: {},
+  pluginsId: [],
+};
+
+describe('plugins reducer', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<PluginsState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('plugins', pluginsReducer);
+  });
+
+  it('should match initial state', () => {
+    const action = { type: 'unknown' };
+
+    expect(pluginsReducer(undefined, action)).toEqual(INITIAL_STATE);
+  });
+  it('should remove overlay from store properly', () => {
+    const { type, payload } = store.dispatch(
+      removePlugin({
+        pluginId: 'hash1',
+      }),
+    );
+
+    expect(type).toBe('plugins/removePlugin');
+    expect(payload).toEqual({ pluginId: 'hash1' });
+  });
+  it('should update store after successful registerPlugin query', async () => {
+    mockedAxiosClient.onPost(apiPath.registerPluign()).reply(HttpStatusCode.Ok, pluginFixture);
+
+    const { type } = await store.dispatch(
+      registerPlugin({
+        hash: pluginFixture.hash,
+        isPublic: pluginFixture.isPublic,
+        pluginName: pluginFixture.name,
+        pluginUrl: pluginFixture.urls[0],
+        pluginVersion: pluginFixture.version,
+      }),
+    );
+
+    expect(type).toBe('plugins/registerPlugin/fulfilled');
+    const { data, pluginsId } = store.getState().plugins;
+
+    expect(data[pluginFixture.hash]).toEqual(pluginFixture);
+    expect(pluginsId).toContain(pluginFixture.hash);
+  });
+
+  it('should update store after failed registerPlugin query', async () => {
+    mockedAxiosClient.onPost(apiPath.registerPluign()).reply(HttpStatusCode.NotFound, undefined);
+
+    const { type, payload } = await store.dispatch(
+      registerPlugin({
+        hash: pluginFixture.hash,
+        isPublic: pluginFixture.isPublic,
+        pluginName: pluginFixture.name,
+        pluginUrl: pluginFixture.urls[0],
+        pluginVersion: pluginFixture.version,
+      }),
+    );
+
+    expect(type).toBe('plugins/registerPlugin/rejected');
+    expect(payload).toEqual(undefined);
+    const { data, pluginsId } = store.getState().plugins;
+
+    expect(data).toEqual({});
+
+    expect(pluginsId).not.toContain(pluginFixture.hash);
+  });
+
+  it('should update store on loading registerPlugin query', async () => {
+    mockedAxiosClient.onPost(apiPath.registerPluign()).reply(HttpStatusCode.NotFound, undefined);
+
+    store.dispatch(
+      registerPlugin({
+        hash: pluginFixture.hash,
+        isPublic: pluginFixture.isPublic,
+        pluginName: pluginFixture.name,
+        pluginUrl: pluginFixture.urls[0],
+        pluginVersion: pluginFixture.version,
+      }),
+    );
+
+    const { data, pluginsId } = store.getState().plugins;
+
+    expect(data).toEqual({});
+    expect(pluginsId).toContain(pluginFixture.hash);
+  });
+});
diff --git a/src/redux/plugins/plugins.reducers.ts b/src/redux/plugins/plugins.reducers.ts
index 4ce46c2417cea0b4aed58c89b12648a865c70056..af01fcfcd7d2ba2ea63be763e828ea1cea6cc4ba 100644
--- a/src/redux/plugins/plugins.reducers.ts
+++ b/src/redux/plugins/plugins.reducers.ts
@@ -20,11 +20,7 @@ export const registerPluginReducer = (builder: ActionReducerMapBuilder<PluginsSt
       state.data[hash] = action.payload;
     }
   });
-  builder.addCase(registerPlugin.rejected, (state, action) => {
-    if (action.payload) {
-      const { hash } = action.meta.arg;
-
-      state.pluginsId = state.pluginsId.filter(pluginId => pluginId !== hash);
-    }
+  builder.addCase(registerPlugin.rejected, state => {
+    state.pluginsId = [];
   });
 };
diff --git a/src/redux/plugins/plugins.thunk.test.ts b/src/redux/plugins/plugins.thunk.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2ae3d53f8d9c0df763d0d3241b38e654692a3ba
--- /dev/null
+++ b/src/redux/plugins/plugins.thunk.test.ts
@@ -0,0 +1,63 @@
+/* eslint-disable no-magic-numbers */
+import axios, { HttpStatusCode } from 'axios';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import MockAdapter from 'axios-mock-adapter';
+import { pluginFixture } from '@/models/fixtures/pluginFixture';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { apiPath } from '../apiPath';
+import { PluginsState } from './plugins.types';
+import pluginsReducer from './plugins.slice';
+import { getInitPlugins } from './plugins.thunk';
+
+const mockedAxiosApiClient = mockNetworkResponse();
+const mockedAxiosClient = new MockAdapter(axios);
+
+describe('plugins - thunks', () => {
+  describe('getInitPlugins', () => {
+    let store = {} as ToolkitStoreWithSingleSlice<PluginsState>;
+    beforeEach(() => {
+      store = createStoreInstanceUsingSliceReducer('plugins', pluginsReducer);
+    });
+    const setHashedPluginMock = jest.fn();
+
+    beforeEach(() => {
+      setHashedPluginMock.mockClear();
+    });
+
+    it('should fetch and load initial plugins', async () => {
+      mockedAxiosApiClient.onPost(apiPath.registerPluign()).reply(HttpStatusCode.Ok, pluginFixture);
+      mockedAxiosApiClient
+        .onGet(apiPath.getPlugin(pluginFixture.hash))
+        .reply(HttpStatusCode.Ok, pluginFixture);
+      mockedAxiosClient.onGet(pluginFixture.urls[0]).reply(HttpStatusCode.Ok, '');
+
+      await store.dispatch(
+        getInitPlugins({
+          pluginsId: [pluginFixture.hash],
+          setHashedPlugin: setHashedPluginMock,
+        }),
+      );
+
+      expect(setHashedPluginMock).toHaveBeenCalledTimes(1);
+    });
+    it('should not load plugin if fetched plugin is not valid', async () => {
+      mockedAxiosApiClient.onPost(apiPath.registerPluign()).reply(HttpStatusCode.NotFound, {});
+      mockedAxiosApiClient
+        .onGet(apiPath.getPlugin(pluginFixture.hash))
+        .reply(HttpStatusCode.NotFound, {});
+      mockedAxiosClient.onGet(pluginFixture.urls[0]).reply(HttpStatusCode.NotFound, '');
+
+      await store.dispatch(
+        getInitPlugins({
+          pluginsId: [pluginFixture.hash],
+          setHashedPlugin: setHashedPluginMock,
+        }),
+      );
+
+      expect(setHashedPluginMock).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts
index e7b83da91015af644a20e7d39c949ec5580a5049..bfb6570be66aa04a3c47be5f9c9a26a46451b25f 100644
--- a/src/redux/root/root.fixtures.ts
+++ b/src/redux/root/root.fixtures.ts
@@ -20,7 +20,7 @@ import { USER_INITIAL_STATE_MOCK } from '../user/user.mock';
 import { STATISTICS_STATE_INITIAL_MOCK } from '../statistics/statistics.mock';
 import { COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK } from '../compartmentPathways/compartmentPathways.mock';
 import { EXPORT_INITIAL_STATE_MOCK } from '../export/export.mock';
-import { PLUGINS_INITIAL_STATE_MOCK } from '../plugins/overlays.mock';
+import { PLUGINS_INITIAL_STATE_MOCK } from '../plugins/plugins.mock';
 
 export const INITIAL_STORE_STATE_MOCK: RootState = {
   search: SEARCH_STATE_INITIAL_MOCK,
diff --git a/src/services/pluginsManager/pluginsManager.test.ts b/src/services/pluginsManager/pluginsManager.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f9b55d2712e3cb23ec806e3440d373fd06bcd3d3
--- /dev/null
+++ b/src/services/pluginsManager/pluginsManager.test.ts
@@ -0,0 +1,75 @@
+/* eslint-disable no-magic-numbers */
+import { store } from '@/redux/store';
+import { configurationFixture } from '@/models/fixtures/configurationFixture';
+import { configurationMapper } from './pluginsManager.utils';
+import { PluginsManager } from './pluginsManager';
+
+jest.mock('../../redux/store');
+
+describe('PluginsManager', () => {
+  const originalWindow = { ...global.window };
+
+  beforeEach(() => {
+    global.window = { ...originalWindow };
+  });
+
+  afterEach(() => {
+    global.window = originalWindow;
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('setHashedPlugin correctly computes hash and updates hashedPlugins', () => {
+    const pluginUrl = 'https://example.com/plugin.js';
+    const pluginScript = 'console.log("Hello, Plugin!");';
+
+    PluginsManager.setHashedPlugin({ pluginUrl, pluginScript });
+
+    expect(PluginsManager.hashedPlugins[pluginUrl]).toBe('edc7eeafccc9e1ab66f713298425947b');
+  });
+
+  it('init subscribes to store changes and updates minerva configuration', () => {
+    (store.getState as jest.Mock).mockReturnValueOnce({
+      configuration: { main: { data: configurationFixture } },
+    });
+
+    PluginsManager.init();
+
+    expect(store.subscribe).toHaveBeenCalled();
+
+    // Simulate store change
+    (store.subscribe as jest.Mock).mock.calls[0][0]();
+
+    expect(store.getState).toHaveBeenCalled();
+    expect(window.minerva.configuration).toEqual(configurationMapper(configurationFixture));
+  });
+  it('init does not update minerva configuration when configuration is undefined', () => {
+    (store.getState as jest.Mock).mockReturnValueOnce({
+      configuration: { main: { data: undefined } },
+    });
+
+    PluginsManager.init();
+
+    expect(store.subscribe).toHaveBeenCalled();
+
+    // Simulate store change
+    (store.subscribe as jest.Mock).mock.calls[0][0]();
+
+    expect(store.getState).toHaveBeenCalled();
+    expect(window.minerva.configuration).toBeUndefined();
+  });
+
+  it('registerPlugin dispatches action and returns element', () => {
+    const pluginName = 'TestPlugin';
+    const pluginVersion = '1.0.0';
+    const pluginUrl = 'https://example.com/test-plugin.js';
+
+    const result = PluginsManager.registerPlugin({ pluginName, pluginVersion, pluginUrl });
+
+    expect(store.dispatch).toHaveBeenCalled();
+
+    expect(result.element).toBeDefined();
+  });
+});