diff --git a/__tests__/src/lib/MiradorViewer.test.js b/__tests__/src/lib/MiradorViewer.test.js
deleted file mode 100644
index 5eb139764d36d3e5ca43cb6f3ba0e7063806461b..0000000000000000000000000000000000000000
--- a/__tests__/src/lib/MiradorViewer.test.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import ReactDOM from 'react-dom';
-import { pluginStore } from '../../../src/extend';
-import MiradorViewer from '../../../src/lib/MiradorViewer';
-
-jest.unmock('react-i18next');
-jest.mock('../../../src/extend');
-jest.mock('react-dom');
-
-describe('MiradorViewer', () => {
-  let instance;
-  beforeAll(() => {
-    ReactDOM.render = jest.fn();
-    instance = new MiradorViewer({});
-  });
-  describe('constructor', () => {
-    it('returns viewer actions', () => {
-      expect(instance.actions.addWindow).toBeDefined();
-    });
-    it('returns viewer store', () => {
-      expect(instance.store.dispatch).toBeDefined();
-    });
-    it('renders via ReactDOM', () => {
-      expect(ReactDOM.render).toHaveBeenCalled();
-    });
-  });
-  describe('process plugins', () => {
-    it('should store plugins and set reducers to state', () => {
-      /** */ const fooReducer = (state = 0) => state;
-      /** */ const barReducer = (state = 0) => state;
-      /** */ const bazReducer = (state = 0) => state;
-      /** */ const plugins = [
-        {
-          reducers: {
-            foo: fooReducer,
-            bar: barReducer,
-          },
-        },
-        {
-          reducers: {
-            baz: bazReducer,
-          },
-        },
-      ];
-      instance = new MiradorViewer({}, plugins);
-      expect(pluginStore.storePlugins).toBeCalledWith(plugins);
-      expect(instance.store.getState().foo).toBeDefined();
-      expect(instance.store.getState().bar).toBeDefined();
-      expect(instance.store.getState().baz).toBeDefined();
-    });
-  });
-  describe('processConfig', () => {
-    it('transforms config values to actions to dispatch to store', () => {
-      instance = new MiradorViewer({
-        id: 'mirador',
-        windows: [
-          {
-            loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
-            canvasIndex: 2,
-          },
-          {
-            loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
-            thumbnailNavigationPosition: 'off',
-            view: 'book',
-          },
-        ],
-        manifests: {
-          'http://media.nga.gov/public/manifests/nga_highlights.json': { provider: 'National Gallery of Art' },
-        },
-      });
-
-      const { windows, manifests } = instance.store.getState();
-      const windowIds = Object.keys(windows);
-      expect(Object.keys(windowIds).length).toBe(2);
-      expect(windows[windowIds[0]].canvasIndex).toBe(2);
-      expect(windows[windowIds[1]].canvasIndex).toBe(0);
-      expect(windows[windowIds[0]].thumbnailNavigationPosition).toBe('far-bottom');
-      expect(windows[windowIds[1]].thumbnailNavigationPosition).toBe('off');
-      expect(windows[windowIds[0]].view).toBe('single');
-      expect(windows[windowIds[1]].view).toBe('book');
-
-      const manifestIds = Object.keys(manifests);
-      expect(Object.keys(manifestIds).length).toBe(2);
-      expect(manifests['http://media.nga.gov/public/manifests/nga_highlights.json'].provider).toBe('National Gallery of Art');
-    });
-  });
-});
diff --git a/__tests__/src/lib/miradorViewer.test.js b/__tests__/src/lib/miradorViewer.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..4f5dc63d4f347eb4b05f9cb106ac824342f9beb1
--- /dev/null
+++ b/__tests__/src/lib/miradorViewer.test.js
@@ -0,0 +1,181 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import deepmerge from 'deepmerge';
+import miradorViewer from '../../../src/lib/miradorViewer';
+
+jest.mock('react-dom', () => ({
+  render: jest.fn(),
+}));
+
+jest.mock('react-redux', () => ({
+  Provider: jest.fn(),
+}));
+
+const config = {
+  id: 'mirador',
+  windows: [
+    {
+      loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
+      canvasIndex: 2,
+    },
+    {
+      loadedManifest: 'https://iiif.bodleian.ox.ac.uk/iiif/manifest/e32a277e-91e2-4a6d-8ba6-cc4bad230410.json',
+      thumbnailNavigationPosition: 'off',
+      view: 'book',
+    },
+  ],
+  manifests: {
+    'https://media.nga.gov/public/manifests/nga_highlights.json': { provider: 'National Gallery of Art' },
+    'https://data.ucd.ie/api/img/manifests/ucdlib:33064': { provider: 'Irish Architectural Archive' },
+  },
+};
+
+const settings = {
+  window: {
+    defaultView: 'single',
+  },
+  thumbnailNavigation: {
+    defaultPosition: 'bottom',
+    height: 150,
+    width: 100,
+  },
+};
+
+/** */ const fooReducer = x => x;
+/** */ const barReducer = x => x;
+/** */ const bazReducer = x => x;
+
+const plugins = [
+  {
+    reducers: {
+      foo: fooReducer,
+      bar: barReducer,
+    },
+  },
+  {
+    reducers: {
+      baz: bazReducer,
+    },
+  },
+];
+
+const pluginStore = {
+  storePlugins: jest.fn(),
+};
+
+const store = {
+  dispatch: jest.fn(),
+};
+
+const createStore = jest.fn().mockReturnValue(store);
+
+const actions = {
+  setConfig: jest.fn().mockReturnValue('RETVAL_SET_CONFIG'),
+  addWindow: jest.fn().mockReturnValue('RETVAL_ADD_WINDOW'),
+  fetchManifest: jest.fn().mockReturnValue('RETVAL_FETCH_MANIFEST'),
+  requestManifest: jest.fn().mockReturnValue('RETVAL_REQUEST_MANIFEST'),
+};
+
+/** */const App = props => null;
+
+/**
+* Finally invoke function under test
+*/
+const retval = miradorViewer({
+  config,
+  settings,
+  plugins,
+  pluginStore,
+  createStore,
+  actions,
+  App,
+});
+
+it('should store plugins', () => {
+  expect(pluginStore.storePlugins).toBeCalledWith(plugins);
+});
+
+it('should create store and pass plugin reducers', () => {
+  const pluginReducers = {
+    foo: fooReducer,
+    bar: barReducer,
+    baz: bazReducer,
+  };
+  expect(createStore).toBeCalledWith(pluginReducers);
+});
+
+it('should merge settings and config and write it to state', () => {
+  const merged = deepmerge(settings, config);
+  expect(actions.setConfig).toBeCalledWith(merged);
+  expect(store.dispatch).nthCalledWith(1, 'RETVAL_SET_CONFIG');
+});
+
+it('should fetch manifest for each window in config', () => {
+  expect(actions.fetchManifest)
+    .toBeCalledWith(config.windows[0].loadedManifest);
+  expect(actions.fetchManifest)
+    .toBeCalledWith(config.windows[1].loadedManifest);
+  expect(store.dispatch)
+    .nthCalledWith(2, 'RETVAL_FETCH_MANIFEST');
+  expect(store.dispatch)
+    .nthCalledWith(4, 'RETVAL_FETCH_MANIFEST');
+});
+
+it('should create a window in state for each window in config', () => {
+  expect(actions.addWindow).toBeCalledTimes(2);
+  expect(store.dispatch).nthCalledWith(3, 'RETVAL_ADD_WINDOW');
+  expect(store.dispatch).nthCalledWith(5, 'RETVAL_ADD_WINDOW');
+});
+
+it('should set correct canvas index to windows in state', () => {
+  expect(actions.addWindow.mock.calls[0][0].canvasIndex).toBe(2);
+  expect(actions.addWindow.mock.calls[1][0].canvasIndex).toBe(0);
+});
+
+it('should set correct manifest id to windows in state', () => {
+  expect(actions.addWindow.mock.calls[0][0].manifestId)
+    .toBe(config.windows[0].loadedManifest);
+  expect(actions.addWindow.mock.calls[1][0].manifestId)
+    .toBe(config.windows[1].loadedManifest);
+});
+
+it('should set correct thumbnail posistion to windows in state', () => {
+  expect(actions.addWindow.mock.calls[0][0].thumbnailNavigationPosition)
+    .toBe('bottom');
+  expect(actions.addWindow.mock.calls[1][0].thumbnailNavigationPosition)
+    .toBe('off');
+});
+
+it('should set correct view type to windows in state', () => {
+  expect(actions.addWindow.mock.calls[0][0].view).toBe('single');
+  expect(actions.addWindow.mock.calls[1][0].view).toBe('book');
+});
+
+it('should "request manifest" for each manifest in config', () => {
+  expect(actions.requestManifest).nthCalledWith(
+    1,
+    'https://media.nga.gov/public/manifests/nga_highlights.json',
+    { provider: 'National Gallery of Art' },
+  );
+  expect(actions.requestManifest).nthCalledWith(
+    2,
+    'https://data.ucd.ie/api/img/manifests/ucdlib:33064',
+    { provider: 'Irish Architectural Archive' },
+  );
+  expect(store.dispatch).nthCalledWith(6, 'RETVAL_REQUEST_MANIFEST');
+  expect(store.dispatch).nthCalledWith(7, 'RETVAL_REQUEST_MANIFEST');
+});
+
+it('should render app and provide it with the store', () => {
+  const AppWithStore = (
+    <Provider store={store}>
+      <App />
+    </Provider>
+  );
+  expect(ReactDOM.render.mock.calls[0][0]).toEqual(AppWithStore);
+});
+
+it('should return store and actions', () => {
+  expect(retval).toEqual({ store, actions });
+});
diff --git a/src/index.js b/src/index.js
index 660bc6849bf716a21fe2c8ee274ee37ad25cb6d3..610580f6ed626235071e98859ea9aa9eb1b79ad5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import init from './init';
+import { initViewer } from './init';
 import * as actions from './state/actions';
 import * as selectors from './state/selectors';
 
@@ -6,7 +6,7 @@ export * from './components';
 export * from './state/reducers';
 
 const exports = {
-  viewer: init,
+  viewer: initViewer,
   actions,
   selectors,
 };
diff --git a/src/init.js b/src/init.js
index 0f3f58b73db07565bd79be0d6dd4e83e8490fb91..d9c9bde7eb845994d7eb84d9121a586ef56c9655 100644
--- a/src/init.js
+++ b/src/init.js
@@ -1,8 +1,21 @@
-import MiradorViewer from './lib/MiradorViewer';
-
+import miradorViewer from './lib/miradorViewer';
+import settings from './config/settings';
+import { pluginStore } from './extend';
+import createStore from './state/createStore';
+import * as actions from './state/actions';
+import App from './containers/App';
+import './styles/index.scss';
 /**
- * Default Mirador instantiation
+ * Init mirador viewer
  */
-export default function (config, plugins) {
-  return new MiradorViewer(config, plugins);
+export function initViewer(config, plugins) {
+  return miradorViewer({
+    config,
+    settings,
+    plugins,
+    pluginStore,
+    createStore,
+    actions,
+    App,
+  });
 }
diff --git a/src/lib/MiradorViewer.js b/src/lib/MiradorViewer.js
deleted file mode 100644
index e9cf6296f624e6dc55e5adbdb2c640a36ed9774b..0000000000000000000000000000000000000000
--- a/src/lib/MiradorViewer.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { Provider } from 'react-redux';
-import deepmerge from 'deepmerge';
-import App from '../containers/App';
-import { pluginStore } from '../extend';
-import createStore from '../state/createStore';
-import * as actions from '../state/actions';
-import settings from '../config/settings';
-
-/**
- * Default Mirador instantiation
- */
-class MiradorViewer {
-  /**
-   */
-  constructor(config, plugins) {
-    pluginStore.storePlugins(plugins);
-    const pluginReducers = getReducersFromPlugins(plugins);
-    this.store = createStore(pluginReducers);
-    this.config = config;
-    this.processConfig();
-    const viewer = {
-      actions,
-      store: this.store,
-    };
-
-    ReactDOM.render(
-      <Provider store={this.store}>
-        <App />
-      </Provider>,
-      document.getElementById(config.id),
-    );
-
-    return viewer;
-  }
-
-  /**
-   * Process config into actions
-   */
-  processConfig() {
-    const mergedConfig = deepmerge(settings, this.config);
-    const action = actions.setConfig(mergedConfig);
-    this.store.dispatch(action);
-
-    mergedConfig.windows.forEach((miradorWindow) => {
-      let thumbnailNavigationPosition;
-      let view;
-      if (miradorWindow.thumbnailNavigationPosition !== undefined) {
-        ({ thumbnailNavigationPosition } = miradorWindow);
-      } else {
-        thumbnailNavigationPosition = mergedConfig.thumbnailNavigation.defaultPosition;
-      }
-      if (miradorWindow.view !== undefined) {
-        ({ view } = miradorWindow);
-      } else {
-        view = mergedConfig.window.defaultView;
-      }
-      this.store.dispatch(actions.fetchManifest(miradorWindow.loadedManifest));
-      this.store.dispatch(actions.addWindow({
-        canvasIndex: (miradorWindow.canvasIndex || 0),
-        manifestId: miradorWindow.loadedManifest,
-        view,
-        thumbnailNavigationPosition,
-      }));
-    });
-
-    Object.keys(mergedConfig.manifests || {}).forEach((manifestId) => {
-      this.store.dispatch(
-        actions.requestManifest(manifestId, mergedConfig.manifests[manifestId]),
-      );
-    });
-  }
-}
-
-/** Return reducers from plugins */
-function getReducersFromPlugins(plugins) {
-  return plugins && plugins.reduce((acc, plugin) => ({ ...acc, ...plugin.reducers }), {});
-}
-
-export default MiradorViewer;
diff --git a/src/lib/miradorViewer.js b/src/lib/miradorViewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..62747d2a89f4f764d5c998cb6fbc7bc78a996964
--- /dev/null
+++ b/src/lib/miradorViewer.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import deepmerge from 'deepmerge';
+
+/** Default Mirador instantiation */
+export default function ({
+  config,
+  settings,
+  plugins,
+  pluginStore,
+  createStore,
+  actions,
+  App,
+}) {
+  pluginStore.storePlugins(plugins);
+  const store = createStore(getReducersFromPlugins(plugins));
+  processConfig(store, actions, config, settings);
+
+  ReactDOM.render(
+    <Provider store={store}>
+      <App />
+    </Provider>,
+    document.getElementById(config.id),
+  );
+
+  return { store, actions };
+}
+
+/** Return reducers from plugins */
+function getReducersFromPlugins(plugins) {
+  return plugins && plugins.reduce((acc, plugin) => ({ ...acc, ...plugin.reducers }), {});
+}
+
+/** Process config */
+function processConfig(store, actions, config, settings) { // eslint-disable-line no-shadow
+  const mergedConfig = writeConfigToState(store, actions, config, settings);
+  processWindowsFromConfig(store, actions, mergedConfig);
+  processManifestsFromConfig(store, actions, mergedConfig);
+}
+
+/** Write config to state */
+function writeConfigToState(store, actions, config, settings) { // eslint-disable-line no-shadow
+  const mergedConfig = deepmerge(settings, config);
+  store.dispatch(actions.setConfig(mergedConfig));
+  return mergedConfig;
+}
+
+/** Process windows from config */
+function processWindowsFromConfig(store, actions, config) { // eslint-disable-line no-shadow
+  config.windows.forEach((win) => {
+    store.dispatch(actions.fetchManifest(win.loadedManifest));
+    store.dispatch(actions.addWindow({
+      canvasIndex: win.canvasIndex || 0,
+      manifestId: win.loadedManifest,
+      thumbnailNavigationPosition:
+        win.thumbnailNavigationPosition || config.thumbnailNavigation.defaultPosition,
+      view: win.view || config.window.defaultView,
+    }));
+  });
+}
+
+/** Process manifests from config */
+function processManifestsFromConfig(store, actions, config) { // eslint-disable-line no-shadow
+  Object.keys(config.manifests || {}).forEach((manifestId) => {
+    store.dispatch(actions.requestManifest(manifestId, config.manifests[manifestId]));
+  });
+}