diff --git a/brian_dashboard_manager/app.py b/brian_dashboard_manager/app.py
index 5bc14b921b5f95fb5c68b08e83b5491952de018e..347dbeea0a7460e6274685db7417ca2b0176f041 100644
--- a/brian_dashboard_manager/app.py
+++ b/brian_dashboard_manager/app.py
@@ -2,9 +2,8 @@
 default app creation
 """
 import brian_dashboard_manager
-from brian_dashboard_manager import environment, CONFIG_KEY
+from brian_dashboard_manager import CONFIG_KEY
 
-environment.setup_logging()
 app = brian_dashboard_manager.create_app()
 
 if __name__ == "__main__":
diff --git a/brian_dashboard_manager/config.py b/brian_dashboard_manager/config.py
index b22ef4e4d11e7ce2764cd9b04ffdfb2f1791685b..81e47202158b056c85f355861cedfa0d41e4e429 100644
--- a/brian_dashboard_manager/config.py
+++ b/brian_dashboard_manager/config.py
@@ -148,7 +148,7 @@ DEFAULT_ORGANIZATIONS = [
 ]
 
 CONFIG_SCHEMA = {
-    "$schema": "https://json-schema.org/draft-07/schema#",
+    "$schema": "http://json-schema.org/draft-07/schema#",
 
     "definitions": {
         "influx-datasource": {
diff --git a/brian_dashboard_manager/grafana/dashboard.py b/brian_dashboard_manager/grafana/dashboard.py
index 37b67163e27fc33c19fe325801b4781e7489d772..74723ee0cc89d306a81bf3e1120debbe971f3192 100644
--- a/brian_dashboard_manager/grafana/dashboard.py
+++ b/brian_dashboard_manager/grafana/dashboard.py
@@ -55,7 +55,7 @@ def delete_dashboard(request: TokenRequest, dashboard: dict, folder_id=None):
             dash = _search_dashboard(request, dashboard, folder_id)
             if dash is None:
                 return True
-            uid = dash.get('dashboard', {}).get('uid', '')
+            uid = dash.get('uid', '')
             if uid:
                 return _delete_dashboard(request, uid)
             else:
@@ -188,7 +188,7 @@ def _get_dashboard(request: TokenRequest, uid):
         r = request.get(f'api/dashboards/uid/{uid}')
     except HTTPError:
         return None
-    return r.json()
+    return r.json()['dashboard']
 
 
 def create_dashboard(request: TokenRequest, dashboard: dict, folder_id=None):
@@ -211,7 +211,7 @@ def create_dashboard(request: TokenRequest, dashboard: dict, folder_id=None):
     # The title might not match the one that's provisioned with that UID.
     # Try to find it by searching for the title instead.
     if existing_dashboard is not None:
-        grafana_title = existing_dashboard['dashboard']['title']
+        grafana_title = existing_dashboard['title']
         different = grafana_title != title
     else:
         different = False
@@ -220,12 +220,12 @@ def create_dashboard(request: TokenRequest, dashboard: dict, folder_id=None):
         existing_dashboard = _search_dashboard(request, dashboard, folder_id)
 
     if existing_dashboard:
-        dashboard['uid'] = existing_dashboard['dashboard']['uid']
-        dashboard['id'] = existing_dashboard['dashboard']['id']
-        dashboard['version'] = existing_dashboard['dashboard']['version']
+        dashboard['uid'] = existing_dashboard['uid']
+        dashboard['id'] = existing_dashboard['id']
+        dashboard['version'] = existing_dashboard['version']
     else:
         # We are creating a new dashboard, delete ID if it exists.
-        del dashboard['id']
+        dashboard.pop('id', None)
 
     payload = {
         'dashboard': dashboard,
@@ -241,14 +241,17 @@ def create_dashboard(request: TokenRequest, dashboard: dict, folder_id=None):
             return r.json()
         except HTTPError as e:
             message = ''
-            if e.response is not None and e.response.status_code < 500:
+            if e.response is not None:
                 # log the error message from Grafana
-                message = e.response.json()
+                try:
+                    message = e.response.json()
+                except json.JSONDecodeError:
+                    message = e.response.text
+            logger.exception(f"Error when provisioning dashboard {title}: {message}")
 
             # only retry on server side errors
             if e.response is not None and e.response.status_code < 500:
                 break
 
-            logger.exception(f'Error when provisioning dashboard {title}: {message}')
         time.sleep(1)  # sleep for 1 second before retrying
     return None
diff --git a/brian_dashboard_manager/grafana/organization.py b/brian_dashboard_manager/grafana/organization.py
index 32d458325b5680bb6f3afdc3f590b11d53d945ad..629fe8a956187f021470fa3a610a3116e63b0246 100644
--- a/brian_dashboard_manager/grafana/organization.py
+++ b/brian_dashboard_manager/grafana/organization.py
@@ -104,7 +104,7 @@ def delete_api_token(request: AdminRequest, token_id: int, org_id=None):
     :return: delete response
     """
 
-    assert token_id
+    assert token_id is not None
     if org_id:
         switch_active_organization(request, org_id)
     result = request.delete(f'api/auth/keys/{token_id}')
diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py
index 8087026120e9a57352257a8bce0474353fe72493..aeab79ee54f7fd26d82c5298c43ce6c88c11872c 100644
--- a/brian_dashboard_manager/grafana/provision.py
+++ b/brian_dashboard_manager/grafana/provision.py
@@ -725,7 +725,7 @@ def provision_maybe(config):
             write_timestamp(now.timestamp(), False)
 
 
-def provision(config):
+def provision(config, raise_exceptions=False):
     """
     The entrypoint for the provisioning process.
 
@@ -827,6 +827,8 @@ def provision(config):
             delete_api_token(request, token['id'], org_id=org_id)
         except Exception:
             logger.exception(f'Error when provisioning org {org["name"]}')
+            if raise_exceptions:
+                raise
             break
 
     logger.info(f'Time to complete: {time.time() - start}')
diff --git a/brian_dashboard_manager/inventory_provider/interfaces.py b/brian_dashboard_manager/inventory_provider/interfaces.py
index 429969405e9b63fbe53c71b40a6be96e8dbabfdf..f37adb6df8964fcf508601eb885c6476e929f8d6 100644
--- a/brian_dashboard_manager/inventory_provider/interfaces.py
+++ b/brian_dashboard_manager/inventory_provider/interfaces.py
@@ -62,7 +62,7 @@ _PORT_TYPES = [t.name for t in list(PORT_TYPES)]
 _INTERFACE_TYPES = [i.name for i in list(INTERFACE_TYPES)]
 
 ROUTER_INTERFACES_SCHEMA = {
-    "$schema": "https://json-schema.org/draft-07/schema#",
+    "$schema": "http://json-schema.org/draft-07/schema#",
     "type": "array",
     "items": {
         "type": "object",
@@ -89,7 +89,7 @@ ROUTER_INTERFACES_SCHEMA = {
 }
 
 INTERFACE_LIST_SCHEMA = {
-    '$schema': 'https://json-schema.org/draft-07/schema#',
+    '$schema': 'http://json-schema.org/draft-07/schema#',
 
     'definitions': {
         'service': {
@@ -137,7 +137,7 @@ INTERFACE_LIST_SCHEMA = {
 }
 
 GWS_DIRECT_DATA_SCHEMA = {
-    '$schema': 'https://json-schema.org/draft-07/schema#',
+    '$schema': 'http://json-schema.org/draft-07/schema#',
 
     'definitions': {
         'oid': {
@@ -214,7 +214,7 @@ GWS_DIRECT_DATA_SCHEMA = {
 }
 
 MULTICAST_SUBSCRIPTION_LIST_SCHEMA = {
-    '$schema': 'https://json-schema.org/draft-07/schema#',
+    '$schema': 'http://json-schema.org/draft-07/schema#',
 
     'definitions': {
         'ipv4-address': {
diff --git a/brian_dashboard_manager/routes/update.py b/brian_dashboard_manager/routes/update.py
index 56ada82117d92c40804f694e7240983ab73733c6..4674d82c85041886e04c692e35cc8debe58c72b2 100644
--- a/brian_dashboard_manager/routes/update.py
+++ b/brian_dashboard_manager/routes/update.py
@@ -13,7 +13,7 @@ from brian_dashboard_manager.config import STATE_PATH
 routes = Blueprint("update", __name__)
 
 UPDATE_RESPONSE_SCHEMA = {
-    '$schema': 'https://json-schema.org/draft-07/schema#',
+    '$schema': 'http://json-schema.org/draft-07/schema#',
     'type': 'object',
     'properties': {
         'message': {
diff --git a/test/conftest.py b/test/conftest.py
index 81a61e687660f713df69414c20a69004898483b9..f071048336179bf35eac2005305bd7272cc9443f 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,8 +1,16 @@
+import copy
+import datetime
+import itertools
 import json
 import os
+import re
+import string
 import tempfile
+from brian_dashboard_manager import environment
+from brian_dashboard_manager.grafana.utils.request import TokenRequest
 import pytest
 import brian_dashboard_manager
+import responses
 
 
 @pytest.fixture
@@ -69,7 +77,305 @@ def data_config_filename(data_config):
 
 
 @pytest.fixture
-def client(data_config_filename):
-    os.environ['CONFIG_FILENAME'] = data_config_filename
+def client(mocker, data_config_filename):
+    mocker.patch.object(environment, "setup_logging")
+    os.environ["CONFIG_FILENAME"] = data_config_filename
     with brian_dashboard_manager.create_app().test_client() as c:
         yield c
+
+
+@pytest.fixture
+def mock_grafana(data_config):
+    def uid_generator():
+        return (
+            "".join(result)
+            for result in itertools.permutations(string.ascii_lowercase, 8)
+        )
+
+    class MockGrafana:
+        def __init__(self) -> None:
+            self.folders = {}
+            self.dashboards = {}
+            self.datasources = {}
+            self.organizations = {}
+            self.api_tokens = {}
+            self.uids = uid_generator()
+            self.ids = itertools.count(start=1)
+            self.request = TokenRequest(data_config["hostname"], token="abc")
+
+        def list_api_tokens(self):
+            return list(copy.copy(val) for val in self.api_tokens.values())
+
+        def create_api_token(self, payload):
+            lifetime = payload.get("secondsToLive")
+            return self._create_object(
+                {
+                    **payload,
+                    "key": "key",
+                    "expiration": (
+                        (
+                            datetime.datetime.utcnow() + datetime.timedelta(lifetime)
+                        ).strftime("%Y-%m-%dT%H:%M:%SZ")
+                        if lifetime is not None
+                        else None
+                    ),
+                },
+                self.api_tokens,
+            )
+
+        def delete_api_token(self, uid):
+            return self._delete_object(uid, self.api_tokens, name="api token")
+
+        def list_organizations(self):
+            return list(copy.copy(val) for val in self.organizations.values())
+
+        def create_organization(self, organization):
+            organization = {
+                **organization,
+                "id": next(self.ids),
+                "uid": str(organization.get("uid") or next(self.uids)),
+            }
+            self.organizations[organization["uid"]] = organization
+            return {
+                "orgId": organization["id"],
+                "message": "Organization created",
+            }
+
+        def delete_organization(self, uid):
+            return self._delete_object(uid, self.organizations, name="organization")
+
+        def list_datasources(self):
+            return list(copy.copy(val) for val in self.datasources.values())
+
+        def create_datasource(self, datasource):
+            return self._create_object(datasource, self.datasources)
+
+        def delete_datasource(self, uid):
+            return self._delete_object(uid, self.datasources, name="datasource")
+
+        def list_folders(self):
+            return list(self.folders.values())
+
+        def create_folder(self, folder):
+            return self._create_object(folder, self.folders)
+
+        def delete_folder(self, uid):
+            return self._delete_object(uid, self.folders, name="folder")
+
+        def list_dashboards(self, title=None, folder_id=None):
+            return [
+                copy.copy(db)
+                for db in self.dashboards.values()
+                if (title is None or db["title"] == title)
+                and (folder_id is None or db.get("folderId") == folder_id)
+            ]
+
+        def create_dashboard(self, dashboard):
+            return self._create_object(dashboard, self.dashboards)
+
+        def delete_dashboard(self, uid):
+            return self._delete_object(uid, self.dashboards, name="dashboard")
+
+        def _create_object(self, obj, all_objects):
+            id = obj.get("id")
+            uid = obj.get("uid")
+            obj = {
+                **obj,
+                "id": id if id is not None else next(self.ids),
+                "uid": str(uid if uid is not None else next(self.uids)),
+            }
+            all_objects[obj["uid"]] = obj
+            return obj
+
+        def _delete_object(self, uid, all_objects, name="object"):
+            del all_objects[uid]
+            return {"message": f"deleted {name}"}
+
+    grafana = MockGrafana()
+
+    url_prefix = f".*{data_config['hostname']}/"
+
+    def json_request_cb(fun):
+        def _wrapped(request):
+            payload = json.loads(request.body) if request.body else None
+            status, result = fun(payload, request)
+            return (status, {}, json.dumps(result))
+
+        return _wrapped
+
+    # --- Api Tokens ---
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + r"api/auth/keys?.+$"),
+        callback=json_request_cb(lambda p, b: (200, grafana.list_api_tokens())),
+    )
+
+    @json_request_cb
+    def create_api_token_callback(payload, request):
+        return (200, grafana.create_api_token(payload))
+
+    responses.add_callback(
+        method=responses.POST,
+        url=re.compile(url_prefix + "api/auth/keys"),
+        callback=create_api_token_callback,
+    )
+
+    @json_request_cb
+    def delete_api_token_callback(payload, request):
+        id = int(request.path_url.split("/")[-1])
+        for uid, ds in grafana.api_tokens.items():
+            if ds["id"] == id:
+                return (200, grafana.delete_api_token(uid))
+        return (404, "")
+
+    responses.add_callback(
+        method=responses.DELETE,
+        url=re.compile(url_prefix + r"api/auth/keys/.+$"),
+        callback=delete_api_token_callback,
+    )
+    # --- Organizations ---
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + "api/orgs"),
+        callback=json_request_cb(lambda p, b: (200, grafana.list_organizations())),
+    )
+
+    @json_request_cb
+    def create_organization_callback(body, _):
+        return (200, grafana.create_organization(body))
+
+    responses.add_callback(
+        method=responses.POST,
+        url=re.compile(url_prefix + "api/orgs"),
+        callback=create_organization_callback,
+    )
+
+    # --- Datasources ---
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + "api/datasources"),
+        callback=json_request_cb(lambda p, b: (200, grafana.list_datasources())),
+    )
+
+    @json_request_cb
+    def create_datasource_callback(payload, _):
+        return (200, grafana.create_datasource(payload))
+
+    responses.add_callback(
+        method=responses.POST,
+        url=re.compile(url_prefix + "api/datasources"),
+        callback=create_datasource_callback,
+    )
+
+    @json_request_cb
+    def delete_datasource_callback(payload, request):
+        name = request.path_url.split("/")[-1]
+        for uid, ds in grafana.datasources.items():
+            if ds["name"] == name:
+                return (200, {}, grafana.delete_datasource(uid))
+        return (404, {}, "")
+
+    responses.add_callback(
+        method=responses.DELETE,
+        url=re.compile(url_prefix + r"api/datasources/name/.+$"),
+        callback=delete_datasource_callback,
+    )
+    # --- Folders ---
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + "api/folders"),
+        callback=json_request_cb(lambda p, b: (200, grafana.list_folders())),
+    )
+
+    @json_request_cb
+    def create_folder_callback(payload, _):
+        return (200, grafana.create_folder(payload))
+
+    responses.add_callback(
+        method=responses.POST,
+        url=re.compile(url_prefix + "api/folders"),
+        callback=create_folder_callback,
+    )
+
+    @json_request_cb
+    def delete_folder_callback(payload, request):
+        uid = request.path_url.split("/")[-1]
+        try:
+            return (200, grafana.delete_folder(uid))
+        except KeyError:
+            return (404, {})
+
+    responses.add_callback(
+        method=responses.DELETE,
+        url=re.compile(url_prefix + r"api/folders/.+$"),
+        callback=delete_folder_callback,
+    )
+
+    # --- Dashboards ---
+    @json_request_cb
+    def get_dashboard_callback(payload, request):
+        uid = request.path_url.split("/")[-1]
+        try:
+            return (200, {"dashboard": grafana.dashboards[uid]})
+        except KeyError:
+            return (404, {})
+
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + r"api/dashboards/uid/.+$"),
+        callback=get_dashboard_callback,
+    )
+
+    @json_request_cb
+    def search_dashboard_callback(_, request):
+        query = request.params
+        print(query)
+        return (
+            200,
+            grafana.list_dashboards(query.get("title"), query.get("folderIds")),
+        )
+
+    responses.add_callback(
+        method=responses.GET,
+        url=re.compile(url_prefix + "api/search"),
+        callback=search_dashboard_callback,
+    )
+
+    @json_request_cb
+    def create_dashboard_callback(payload, request):
+        dashboard = payload["dashboard"]
+
+        return (200, grafana.create_dashboard(dashboard))
+
+    responses.add_callback(
+        method=responses.POST,
+        url=re.compile(url_prefix + "api/dashboards/db"),
+        callback=create_dashboard_callback,
+    )
+
+    @json_request_cb
+    def delete_dashboard_callback(payload, request):
+        uid = request.path_url.split("/")[-1]
+        try:
+            return (200, grafana.delete_dashboard(uid))
+        except KeyError:
+            return (404, {})
+
+    responses.add_callback(
+        method=responses.DELETE,
+        url=re.compile(url_prefix + r"api/dashboards/uid/.+$"),
+        callback=delete_dashboard_callback,
+    )
+
+    # --- Other ---
+    responses.add(
+        method=responses.PUT,
+        url=re.compile(url_prefix + "api/org/preferences"),
+        json={"message": "Preferences updated"},
+    )
+    responses.add(
+        method=responses.POST,
+        url=re.compile(url_prefix + r"api/user/using/.+$"),
+        json={"message": "ok"},
+    )
+    return grafana
diff --git a/test/test_grafana_dashboard.py b/test/test_grafana_dashboard.py
index e3da83f02c59e21ca0d49348e396d11e8b1aec7b..c0534103b0ccdad86cdb82c4dea29d3f5d3da31f 100644
--- a/test/test_grafana_dashboard.py
+++ b/test/test_grafana_dashboard.py
@@ -1,130 +1,73 @@
-import pytest
-import json
-import requests
 import responses
 from brian_dashboard_manager.grafana import dashboard, provision
 from brian_dashboard_manager.grafana.utils.request import TokenRequest
 
 
 @responses.activate
-def test_get_dashboard(data_config):
+def test_get_dashboard(mock_grafana):
+    mock_grafana.create_dashboard({"uid": "1"})
 
-    UID = 1
+    request = mock_grafana.request
 
-    request = TokenRequest(**data_config, token='test')
-
-    responses.add_callback(
-        method=responses.GET,
-        url=request.BASE_URL +
-        f'api/dashboards/uid/{UID}',
-        callback=lambda f: (404, {}, ''))
+    data = dashboard._get_dashboard(request, "1")
+    assert data["uid"] == "1"
 
-    data = dashboard._get_dashboard(request, UID)
+    data = dashboard._get_dashboard(request, "2")
     assert data is None
 
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID+1}',
-        json={'uid': 1})
-
-    data = dashboard._get_dashboard(request, UID + 1)
-    assert data['uid'] == 1
-
 
 @responses.activate
-def test_delete_dashboards(data_config):
-    UID = 1
-    dashboards = [{'uid': UID}]
+def test_delete_dashboards_new(mock_grafana):
+    mock_grafana.create_dashboard({"uid": "1"})
+    mock_grafana.create_dashboard({"uid": "2"})
 
-    request = TokenRequest(**data_config, token='test')
+    request = mock_grafana.request
+    assert dashboard.delete_dashboards(request) is True
+    assert not mock_grafana.dashboards
 
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID}',
-        json=dashboards[0])
-
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/search',
-        json=dashboards)
-
-    def delete_callback(request):
-        uid = request.path_url.split('/')[-1]
-        assert int(uid) == UID
-        return 200, {}, json.dumps({'message': 'Dashboard has been deleted.'})
-
-    responses.add_callback(
-        method=responses.DELETE,
-        url=request.BASE_URL +
-        f'api/dashboards/uid/{UID}',
-        callback=delete_callback)
 
-    data = dashboard.delete_dashboards(request)
-    assert data is True
-
-    responses.add_callback(
-        method=responses.DELETE,
-        url=request.BASE_URL +
-        f'api/dashboards/uid/{UID+1}',
-        callback=lambda f: (400, {}, ''))
-
-    with pytest.raises(requests.HTTPError):
-        data = dashboard._delete_dashboard(request, UID + 1)
+@responses.activate
+def test_delete_nonexsiting_dashboard(mock_grafana):
+    assert not mock_grafana.dashboards
+    assert dashboard._delete_dashboard(mock_grafana.request, "2") is True
 
 
 @responses.activate
-def test_delete_dashboard(data_config):
-    UID = 1
-    ID = 1
+def test_delete_dashboard_by_uid(mock_grafana):
+    UID = "1"
     VERSION = 1
-    FOLDER_ID = 1
-    TITLE = 'testdashboard'
-    dash = {'id': ID, 'uid': UID, 'title': TITLE, 'version': VERSION}
-    request = TokenRequest(**data_config, token='test')
-
-    responses.add(
-        method=responses.DELETE,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID}',
-        json={'message': 'deleted dashboard'})
-
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID}',
-        json={})
-
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/search',
-        json=[dash])
+    TITLE = "testdashboard"
+    dash = {"uid": UID, "title": TITLE, "version": VERSION}
+    mock_grafana.create_dashboard(dash)
+    request = mock_grafana.request
 
-    deleted = dashboard.delete_dashboard(request, dash)
-    assert deleted
-    del dash['uid']
-    deleted = dashboard.delete_dashboard(request, dash, FOLDER_ID)
-    assert deleted
+    assert mock_grafana.dashboards
+    assert dashboard.delete_dashboard(request, dash)
+    assert not mock_grafana.dashboards
 
 
 @responses.activate
-def test_search_dashboard(data_config):
-    UID = 1
-    TITLE = 'testdashboard'
-    dashboards = [{'uid': UID, 'title': TITLE}]
+def test_delete_dashboard_by_title_and_folder(mock_grafana):
+    FOLDER_ID = 1
+    TITLE = "testdashboard"
+    dash = {"title": TITLE, "folderId": FOLDER_ID}
+    mock_grafana.create_dashboard(dash)
+    request = mock_grafana.request
 
-    request = TokenRequest(**data_config, token='test')
+    assert mock_grafana.dashboards
+    assert dashboard.delete_dashboard(request, dash)
+    assert not mock_grafana.dashboards
 
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/search',
-        json=dashboards)
 
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID}',
-        json=dashboards[0])
+@responses.activate
+def test_search_dashboard(mock_grafana):
+    UID = "1"
+    TITLE = "testdashboard"
+    mock_grafana.create_dashboard({"uid": UID, "title": TITLE})
+    request = mock_grafana.request
 
-    data = dashboard._search_dashboard(
-        request, {'title': dashboards[0]['title']})
-    assert data['uid'] == UID
+    data = dashboard._search_dashboard(request, {"title": TITLE})
+    assert data["uid"] == UID
 
     data = dashboard._search_dashboard(request, {'title': 'DoesNotExist'})
     assert data is None
@@ -144,40 +87,48 @@ def test_search_dashboard_error(data_config):
 
 
 @responses.activate
-def test_create_dashboard(data_config):
-    UID = 1
-    ID = 1
+def test_create_dashboard(mock_grafana):
+    UID = "1"
     VERSION = 1
-    TITLE = 'testdashboard'
-    dashboard = {'id': ID, 'uid': UID, 'title': TITLE, 'version': VERSION}
-    request = TokenRequest(**data_config, token='test')
+    TITLE = "testdashboard"
+    dashboard = {"uid": UID, "title": TITLE, "version": VERSION}
+    request = mock_grafana.request
+    assert not mock_grafana.dashboards
 
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + f'api/dashboards/uid/{UID}',
-        json={'dashboard': dashboard})
+    provision.create_dashboard(request, dashboard)
+    assert UID in mock_grafana.dashboards
 
-    responses.add_callback(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/search',
-        callback=lambda f: (400, {}, ''))
 
-    def post_callback(request):
-        body = json.loads(request.body)
-        return 200, {}, json.dumps(body['dashboard'])
+@responses.activate
+def test_create_dashboard_updates_with_existing_uid(mock_grafana):
+    dashboard = {'title': 'testdashboard', 'version': 1}
+    dash = mock_grafana.create_dashboard(dashboard)
+    request = mock_grafana.request
 
-    responses.add_callback(
-        method=responses.POST,
-        url=request.BASE_URL + 'api/dashboards/db',
-        callback=post_callback)
+    dashboard.update(**dash)
+    dashboard['data'] = 'data'
+
+    provision.create_dashboard(request, dashboard)
+    in_grafana = mock_grafana.dashboards[dashboard['uid']]
+    assert in_grafana['data'] == 'data'
+    assert in_grafana['id'] == dashboard['id']
+
+
+@responses.activate
+def test_create_dashboard_no_uid_does_not_send_id(mock_grafana):
+    ID = 1042
+    VERSION = 1
+    TITLE = 'testdashboard'
+    dashboard = {'id': ID, 'title': TITLE, 'version': VERSION}
+    request = mock_grafana.request
 
     data = provision.create_dashboard(request, dashboard)
-    assert data == dashboard
+    assert data['id'] != ID
 
 
 @responses.activate
-def test_create_dashboard_no_uid_error(data_config):
-    ID = 1
+def test_create_dashboard_error(data_config):
+    ID = 1042
     VERSION = 1
     TITLE = 'testdashboard'
     dashboard = {'id': ID, 'title': TITLE, 'version': VERSION}
@@ -189,13 +140,7 @@ def test_create_dashboard_no_uid_error(data_config):
         callback=lambda f: (400, {}, ''))
 
     def post_callback(request):
-        body = json.loads(request.body)
-        # if a dashboard doesn't have an UID, the ID should not be sent to
-        # grafana.
-        assert 'id' not in body['dashboard']
-
-        # have already tested a successful response, respond with error here.
-        return 400, {}, '{}'
+        return 400, {}, ''
 
     responses.add_callback(
         method=responses.POST,
diff --git a/test/test_grafana_datasource.py b/test/test_grafana_datasource.py
index fe939950629514c56ec85862ef3d0e7eda4ad8b1..858c63081bd66243a2f94c80b8605ad38b5c3b5f 100644
--- a/test/test_grafana_datasource.py
+++ b/test/test_grafana_datasource.py
@@ -5,35 +5,23 @@ from brian_dashboard_manager.grafana.utils.request import AdminRequest
 
 
 @responses.activate
-def test_get_datasources(data_config):
-
-    BODY = []
-
-    request = AdminRequest(**data_config)
-
-    responses.add(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/datasources', json=BODY)
+def test_get_datasources(mock_grafana):
+    mock_grafana.create_datasource({"some": "data"})
+    request = mock_grafana.request
 
     data = datasource.get_datasources(request)
-    assert data == BODY
+    assert data[0]["some"] == "data"
 
 
 @responses.activate
-def test_get_missing_datasource_definitions(data_config):
-    # this only retrieves data from the filesystem and checks against
-    # what's configured in grafana.. just make sure
-    # we cover the part for fetching datasources
+def test_get_missing_datasource_definitions(mock_grafana, tmp_path):
+    request = mock_grafana.request
 
-    request = AdminRequest(**data_config)
-
-    responses.add(method=responses.GET, url=request.BASE_URL +
-                  'api/datasources', json={})
-
-    dir = '/tmp/dirthatreallyshouldnotexistsousealonganduniquestring'
-    # it returns a generator, so iterate :)
-    for data in datasource.get_missing_datasource_definitions(request, dir):
-        pass
+    source = {"some": "data"}
+    (tmp_path / "datasource.json").write_text(json.dumps(source))
+    assert list(
+        datasource.get_missing_datasource_definitions(request, str(tmp_path))
+    ) == [source]
 
 
 def test_datasource_provisioned():
@@ -61,9 +49,7 @@ def test_datasource_provisioned():
 
 
 @responses.activate
-def test_create_prod_datasource(data_config):
-    ORG_ID = 1
-
+def test_create_prod_datasource(mock_grafana):
     BODY = {
         "name": "brian-influx-datasource",
         "type": "influxdb",
@@ -75,43 +61,12 @@ def test_create_prod_datasource(data_config):
         "readOnly": False
     }
 
-    request = AdminRequest(**data_config)
-
-    def post_callback(request):
-        body = json.loads(request.body)
-        result = {
-            'datasource': {
-                'id': 1,
-                'orgId': ORG_ID,
-                'type': 'graphite',
-                'typeLogoUrl': '',
-                'password': '',
-                'user': '',
-                'basicAuthUser': '',
-                'basicAuthPassword': '',
-                'withCredentials': False,
-                'jsonData': {},
-                'secureJsonFields': {},
-                'version': 1
-            },
-            'id': 1,
-            'message': 'Datasource added',
-            'name': body['name']
-        }
-        result['datasource'].update(body)
-        return 200, {}, json.dumps(result)
-
-    responses.add_callback(
-        method=responses.POST,
-        url=request.BASE_URL + 'api/datasources',
-        callback=post_callback)
+    request = mock_grafana.request
 
-    data = provision.create_datasource(
-        request, BODY)
+    assert not mock_grafana.datasources
 
-    datasource_type = data['datasource']['type']
-    datasource_config_url = data_config['datasources'][datasource_type]['url']
-    assert data['datasource']['url'] != datasource_config_url
+    result = provision.create_datasource(request, BODY)
+    assert result["uid"] in mock_grafana.datasources
 
 
 @responses.activate
diff --git a/test/test_grafana_folder.py b/test/test_grafana_folder.py
index e9baedc9e356078094bbd1b71b7b2db5f5af74ea..1fff9f194af2e94a3cf9b029e588f6059ec83681 100644
--- a/test/test_grafana_folder.py
+++ b/test/test_grafana_folder.py
@@ -1,8 +1,6 @@
 
-import json
 import responses
 from brian_dashboard_manager.grafana.folder import find_folder
-from brian_dashboard_manager.grafana.utils.request import TokenRequest
 
 
 def generate_folder(data):
@@ -24,26 +22,21 @@ def generate_folder(data):
 
 
 @responses.activate
-def test_find_folder(data_config):
+def test_find_folder(data_config, mock_grafana):
+    TITLE = "testfolder123"
+    request = mock_grafana.request
+    assert not mock_grafana.folders
+    folder = find_folder(request, TITLE, create=True)
+    assert folder["title"] == TITLE
+    assert folder["uid"] in mock_grafana.folders
 
-    TITLE = 'testfolder123'
-
-    request = TokenRequest(**data_config, token='test')
-
-    responses.add(
-        method=responses.GET,
-        url=f"http://{data_config['hostname']}/api/folders",
-        json=[])
 
-    def folder_post(request):
-        data = json.loads(request.body)
-        return 200, {}, json.dumps(generate_folder(data))
-
-    responses.add_callback(
-        method=responses.POST,
-        url=f"http://{data_config['hostname']}/api/folders",
-        callback=folder_post)
+@responses.activate
+def test_find_folder_no_create(data_config, mock_grafana):
+    TITLE = 'testfolder123'
+    request = mock_grafana.request
+    assert not mock_grafana.folders
 
-    folder = find_folder(request, TITLE)
-    assert folder['id'] == 555
-    assert folder['title'] == TITLE
+    folder = find_folder(request, TITLE, create=False)
+    assert folder is None
+    assert not mock_grafana.folders
diff --git a/test/test_grafana_organization.py b/test/test_grafana_organization.py
index c54b38182e2af9427d3289a8515f4fc174513fc0..e9a62280fc6b4a02f1ccd34e0b927e5b8d48cb8a 100644
--- a/test/test_grafana_organization.py
+++ b/test/test_grafana_organization.py
@@ -1,111 +1,51 @@
-import json
 import responses
-from datetime import datetime, timedelta
 from brian_dashboard_manager.grafana import provision
-from brian_dashboard_manager.grafana.utils.request import AdminRequest
 
 
 @responses.activate
-def test_get_organizations(data_config):
-    request = AdminRequest(**data_config)
+def test_get_organizations(mock_grafana):
+    request = mock_grafana.request
 
-    responses.add(method=responses.GET,
-                  url=request.BASE_URL + 'api/orgs',
-                  json=[{'id': 91,
-                         'name': 'Testorg1'},
-                        {'id': 92,
-                         'name': 'GÉANT Testorg2'},
-                        {'id': 93,
-                         'name': 'NRENsTestorg3'},
-                        {'id': 94,
-                         'name': 'General Public'}])
+    mock_grafana.create_organization({"id": 91, "name": "Testorg1"})
+    mock_grafana.create_organization({"id": 91, "name": "Testorg2"})
 
     data = provision.get_organizations(request)
-    assert data is not None
+    assert [o["name"] for o in data] == ["Testorg1", "Testorg2"]
 
 
 @responses.activate
-def test_create_organization(data_config):
+def test_create_organization(mock_grafana):
     ORG_NAME = 'fakeorg123'
+    request = mock_grafana.request
 
-    def post_callback(request):
-        body = json.loads(request.body)
-        assert body['name'] == ORG_NAME
-        return 200, {}, json.dumps(
-            {'orgId': 1, 'message': 'Organization created'})
-
-    request = AdminRequest(**data_config)
-
-    responses.add_callback(
-        method=responses.POST,
-        url=request.BASE_URL + 'api/orgs',
-        callback=post_callback)
+    assert not len(mock_grafana.organizations)
 
     data = provision.create_organization(request, ORG_NAME)
-    assert data is not None
+    assert len(mock_grafana.organizations) == 1
+    assert next(iter(mock_grafana.organizations.values()))["id"] == data["id"]
+    assert data["name"] == ORG_NAME
 
 
 @responses.activate
-def test_delete_expired_api_tokens(data_config):
-    ORG_ID = 1
-    KEY_ID = 1
-
-    def post_callback(request):
-        assert request.params['includeExpired'] == 'True'
-        time = (datetime.now() - timedelta(seconds=60)
-                ).strftime('%Y-%m-%dT%H:%M:%SZ')
-        return 200, {}, json.dumps([{'expiration': time, 'id': KEY_ID}])
-
-    request = AdminRequest(**data_config)
-    responses.add_callback(
-        method=responses.GET,
-        url=request.BASE_URL + 'api/auth/keys',
-        callback=post_callback)
-
-    responses.add(
-        method=responses.POST,
-        url=request.BASE_URL +
-        f'api/user/using/{ORG_ID}',
-        json={
-            "message": "Active organization changed"})
-    responses.add(
-        method=responses.DELETE,
-        url=request.BASE_URL +
-        f'api/auth/keys/{KEY_ID}',
-        json={
-            "message": "API key deleted"})
+def test_delete_expired_api_tokens(mock_grafana):
+    mock_grafana.create_api_token({"secondsToLive": -1})  # an expired token
+    request = mock_grafana.request
 
+    assert len(mock_grafana.api_tokens) == 1
     provision.delete_expired_api_tokens(request)
+    assert not mock_grafana.api_tokens
 
 
 @responses.activate
-def test_create_api_token(data_config):
+def test_create_api_token(mock_grafana):
     ORG_ID = 1
-    TOKEN_ID = 1
     BODY = {
         'name': 'test-token',
         'role': 'Admin',
         'secondsToLive': 3600
     }
-
-    request = AdminRequest(**data_config)
-
-    def post_callback(request):
-        body = json.loads(request.body)
-        assert body == BODY
-        return 200, {}, json.dumps({'id': TOKEN_ID})
-
-    responses.add_callback(
-        method=responses.POST,
-        url=request.BASE_URL + 'api/auth/keys',
-        callback=post_callback)
-
-    responses.add(
-        method=responses.POST,
-        url=request.BASE_URL +
-        f'api/user/using/{ORG_ID}',
-        json={
-            "message": "Active organization changed"})
-
-    data = provision.create_api_token(request, ORG_ID, BODY)
-    assert data['id'] == TOKEN_ID
+    request = mock_grafana.request
+    assert not mock_grafana.api_tokens
+    provision.create_api_token(request, ORG_ID, BODY)
+    assert len(mock_grafana.api_tokens) == 1
+    assert next(iter(mock_grafana.api_tokens.values()))["name"] == "test-token"
diff --git a/test/test_update.py b/test/test_update.py
index 93b5badf5290a36c6635307b2017a80fb57c3d9c..88ce867c3b7528363dede0c5ab378d67962844bd 100644
--- a/test/test_update.py
+++ b/test/test_update.py
@@ -1,9 +1,7 @@
 import pytest
 import responses
-import json
 
-from brian_dashboard_manager.grafana.provision import provision_folder, \
-    provision
+from brian_dashboard_manager.grafana.provision import provision_folder, provision
 from test.conftest import get_test_data
 
 TEST_INTERFACES = [
@@ -623,7 +621,7 @@ def generate_folder(data):
     ],
 )
 def test_provision_nren_folder(
-    folder_name, excluded_nrens, expected_nrens, data_config, mocker
+    folder_name, excluded_nrens, expected_nrens, data_config, mock_grafana
 ):
     dashboards = {
         "NREN": {
@@ -647,35 +645,8 @@ def test_provision_nren_folder(
         json=get_test_data("services.json"),
     )
 
-    # just return a generated folder
-    mocker.patch(
-        "brian_dashboard_manager.grafana.provision.find_folder",
-        return_value=generate_folder({"uid": "testfolderuid", "title": "testfolder"}),
-    )
-
-    def create_dashboard(request, dashboard, folder_id=None):
-        return dashboard
-
-    mocker.patch(
-        "brian_dashboard_manager.grafana.provision.create_dashboard", create_dashboard
-    )
-
-    def _search_dashboard(request, dashboard, folder_id=None):
-        return None
-
-    mocker.patch(
-        "brian_dashboard_manager.grafana.dashboard._search_dashboard", _search_dashboard
-    )
-
-    def delete_dashboard(request, dashboard, folder_id=None):
-        return True
-
-    mocker.patch(
-        "brian_dashboard_manager.grafana.dashboard.delete_dashboard", delete_dashboard
-    )
-
     result = provision_folder(
-        None,
+        mock_grafana.request,
         folder_name,
         dashboards["NREN"],
         data_config,
@@ -692,7 +663,7 @@ def test_provision_nren_folder(
 
 
 @responses.activate
-def test_provision(data_config, mocker, client):
+def test_provision(data_config, mocker, mock_grafana):
 
     responses.add(
         method=responses.GET,
@@ -714,42 +685,8 @@ def test_provision(data_config, mocker, client):
         url=f'{data_config["inventory_provider"]}/poller/eumetsat-multicast',
         json=EUMETSAT_MULTICAST)
 
-    responses.add(
-        method=responses.DELETE,
-        url=f"http://{data_config['hostname']}/api/folders",
-        json={"message": "Deleted folder"})
-
-    responses.add(
-        method=responses.GET,
-        url=f"http://{data_config['hostname']}/api/folders",
-        json=[
-            generate_folder({'uid': 'fakeuid', 'title': 'fakefolder'})])
-
-    def folder_post(request):
-        data = json.loads(request.body)
-        return 200, {}, json.dumps(generate_folder(data))
-
-    responses.add_callback(
-        method=responses.POST,
-        url=f"http://{data_config['hostname']}/api/folders",
-        callback=folder_post)
-
-    def search_responses(request):
-        if request.params.get('query', None) == 'Home':
-            return 200, {}, json.dumps([])
-        if request.params.get('type', None) == 'dash-db':
-            return 200, {}, json.dumps([])
-        assert False  # no other queries expected
-
-    responses.add_callback(
-        method=responses.GET,
-        url=f"http://{data_config['hostname']}/api/search",
-        callback=search_responses)
-
-    responses.add(
-        method=responses.GET,
-        url=f"http://{data_config['hostname']}/api/datasources",
-        json=[{
+    mock_grafana.create_datasource(
+        {
             "name": "brian-influx-datasource",
             "type": "influxdb",
             "access": "proxy",
@@ -757,60 +694,12 @@ def test_provision(data_config, mocker, client):
             "database": "test-db",
             "basicAuth": False,
             "isDefault": True,
-            "readOnly": False
-        }])
-
-    responses.add(
-        method=responses.POST,
-        url=f"http://{data_config['hostname']}/api/dashboards/db",
-        json={'uid': '999', 'id': 666})
-
-    responses.add(
-        method=responses.PUT,
-        url=f"http://{data_config['hostname']}/api/org/preferences",
-        json={'message': 'Preferences updated'})
-
-    def homedashboard(request):
-        return 404, {}, ''
-
-    responses.add_callback(
-        method=responses.GET,
-        url=f"http://{data_config['hostname']}/api/dashboards/uid/home",
-        callback=homedashboard)
-
-    PROVISIONED_ORGANIZATION = {
-        'name': data_config['organizations'][0],
-        'id': 0
-    }
-
-    EXISTING_ORGS = [{**org, 'id': i + 1}
-                     for i, org in enumerate(data_config['organizations'][1:])]
-
-    _mocked_get_organizations = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.get_organizations')
-    # all organizations are provisioned except the first one.
-    _mocked_get_organizations.return_value = EXISTING_ORGS.copy()
-
-    _mocked_create_organization = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_organization')
-
-    # spoof creating first organization
-    _mocked_create_organization.return_value = PROVISIONED_ORGANIZATION
-
-    _mocked_delete_expired_api_tokens = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.delete_expired_api_tokens')
-    # we dont care about this, , tested separately
-    _mocked_delete_expired_api_tokens.return_value = None
-
-    _mocked_create_api_token = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_api_token')
-    _mocked_create_api_token.return_value = {
-        'key': 'testtoken', 'id': 0}  # api token
+            "readOnly": False,
+        }
+    )
 
-    _mocked_create_datasource = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_datasource')
-    # we dont care about this, just mark it created
-    _mocked_create_datasource.return_value = True
+    for org in data_config["organizations"][1:]:
+        mock_grafana.create_organization(org)
 
     _mocked_get_dashboard_definitions = mocker.patch(
         'brian_dashboard_manager.grafana.provision.get_dashboard_definitions')
@@ -832,14 +721,4 @@ def test_provision(data_config, mocker, client):
         dashboard  # test dashboard
     ]
 
-    _mocked_create_dashboard = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.create_dashboard')
-    # we dont care about this, just mark it created
-    # we dont care about this, tested separately
-    _mocked_create_dashboard.return_value = {'uid': '999', 'id': 666}
-
-    _mocked_delete_api_token = mocker.patch(
-        'brian_dashboard_manager.grafana.provision.delete_api_token')
-    # we dont care about this, tested separately
-    _mocked_delete_api_token.return_value = None
-    provision(data_config)
+    provision(data_config, raise_exceptions=True)
diff --git a/tox.ini b/tox.ini
index dc78bc46551ca55171192633bc42e0c3eaaa935f..6b749b99e29ebfbdbe4980c958b1845fba1e7ae5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,13 +10,12 @@ concurrency = multiprocessing,thread
 
 [testenv]
 deps =
-    pytest-xdist
     pytest-cov
     flake8
     -r requirements.txt
 
 commands =
     coverage erase
-    pytest -n auto --cov brian_dashboard_manager --cov-fail-under=80 --cov-report html --cov-report xml --cov-report term -p no:checkdocs
+    pytest --cov brian_dashboard_manager --cov-fail-under=80 --cov-report html --cov-report xml --cov-report term -p no:checkdocs
     flake8
     sphinx-build -M html docs/source docs/build