diff --git a/tests/test_xfel_calibrate/test_cli.py b/tests/test_xfel_calibrate/test_cli.py
index 1376f05c52e11d7e8febafc6d24f1ee7cf1ba23c..4a75d6242c97644a249379cce32a6ec93b38c622 100644
--- a/tests/test_xfel_calibrate/test_cli.py
+++ b/tests/test_xfel_calibrate/test_cli.py
@@ -8,7 +8,6 @@ the current test cases, this should be improved later on.
 
 import ast
 import shlex
-import shutil
 import sys
 from datetime import date
 from pathlib import Path
@@ -325,133 +324,6 @@ class TestIntelliList:
                 assert parameters["concurrency_parameter"][0] == i
 
 
-class TestUserNotebooks:
-    @pytest.fixture(scope="class", autouse=True)
-    def fake_process_calibrate(self):
-        with FakeProcessCalibrate() as fake_process:
-            yield fake_process
-
-    @pytest.fixture(scope="class")
-    def mock_proposal(self, tmpdir_factory):
-        return MockProposal(
-            tmp_path=Path(tmpdir_factory.mktemp("exp")),
-            instrument="AGIPD",
-            cycle="202031",
-            proposal="p900113",
-            runs=1,
-            sequences=1,
-        )
-
-    @pytest.fixture(scope="function")
-    def calibrate_call(self, mock_proposal: MockProposal, capsys, tmp_path: Path):
-        # The user notebook path template for `TEST`/`TEST-USER-NB` is:
-        #    `"/{instrument}/{cycle}/p{proposal}/test-cli.ipynb"`
-        #
-        # So by setting `instrument` to tmp_path/TEST we set the user notebook
-        # location to one in our tmp dir
-
-        self.user_nb = mock_proposal.path_usr / "test-cli.ipynb"
-
-        shutil.copy(
-            Path("./notebooks/test/test-cli.ipynb").absolute(), str(self.user_nb)
-        )
-
-        return CalibrateCall(
-            tmp_path,
-            capsys,
-            in_folder=mock_proposal.path_raw,
-            out_folder=mock_proposal.path_proc,
-            detector="TEST",
-            cal_type="TEST-USER-NB",
-            extra_args=[
-                "--root",
-                str(mock_proposal.path_usr),
-                "--number",
-                "10",
-            ],
-        )
-
-    def test_user_notebook_is_test_notebook(self, calibrate_call: CalibrateCall):
-        assert (
-            Path("./notebooks/test/test-cli.ipynb").read_bytes()
-            == self.user_nb.read_bytes()
-        )
-
-    def test_output_ipynb(self, calibrate_call: CalibrateCall):
-        notebook_path = calibrate_call.paths.notebooks
-        assert len(notebook_path) == 1
-
-        with notebook_path[0].open() as file:
-            notebook = nbformat.read(file, as_version=4)
-
-        parameters = {p.name: p.value for p in extract_parameters(notebook)}
-
-        assert parameters["number"] == 10
-
-
-class TestUserVenv:
-    @pytest.fixture(scope="class", autouse=True)
-    def fake_process_calibrate(self):
-        with FakeProcessCalibrate() as fake_process:
-            yield fake_process
-
-    @pytest.fixture(scope="class")
-    def mock_proposal(self, tmpdir_factory):
-        return MockProposal(
-            tmp_path=Path(tmpdir_factory.mktemp("exp")),
-            instrument="AGIPD",
-            cycle="202031",
-            proposal="p900113",
-            runs=1,
-            sequences=1,
-        )
-
-    @pytest.fixture(scope="function")
-    def calibrate_call(self, mock_proposal: MockProposal, capsys, tmp_path: Path):
-        self.user_nb = mock_proposal.path_usr / "test-cli.ipynb"
-
-        shutil.copy(
-            Path("./notebooks/test/test-cli.ipynb").absolute(), str(self.user_nb)
-        )
-
-        return CalibrateCall(
-            tmp_path,
-            capsys,
-            in_folder=mock_proposal.path_raw,
-            out_folder=mock_proposal.path_proc,
-            detector="TEST",
-            cal_type="TEST-USER-NB-VENV",
-            extra_args=[
-                "--root",
-                str(mock_proposal.path_usr),
-                "--number",
-                "10",
-            ],
-        )
-
-    def test_call(self, calibrate_call: CalibrateCall):
-        assert "Running job in user venv at" in calibrate_call.out
-
-    def test_expected_processes_called(
-        self,
-        mock_proposal: MockProposal,
-        fake_process_calibrate: FakeProcessCalibrate,
-    ):
-        process_calls = [
-            list(shlex.shlex(p, posix=True, punctuation_chars=True))
-            if isinstance(p, str)
-            else p
-            for p in fake_process_calibrate.calls
-        ]
-
-        processes_called = [p[0] for p in process_calls]  # List of the process names
-
-        assert "sbatch" in processes_called
-
-        sbatch_cmd = process_calls[processes_called.index("sbatch")]
-        assert any(str(mock_proposal.path_usr / ".venv") in arg for arg in sbatch_cmd)
-
-
 class TestAgipdNotebook:
     @pytest.fixture(scope="class", autouse=True)
     def fake_process_calibrate(self):
diff --git a/tests/test_xfel_calibrate/test_user_configs.py b/tests/test_xfel_calibrate/test_user_configs.py
new file mode 100644
index 0000000000000000000000000000000000000000..e064ca7311e0f1895169c8166bbf7845c8a9585a
--- /dev/null
+++ b/tests/test_xfel_calibrate/test_user_configs.py
@@ -0,0 +1,144 @@
+# pylint: disable=missing-class-docstring, missing-function-docstring, no-self-use
+""" Tests for user configurable notebooks
+
+These tests cover functionality enabled by setting `"notebook": None` and
+instead providing path templates under the `user` key in the `notebooks.py`
+configuration file.
+"""
+import shlex
+import shutil
+from pathlib import Path
+
+import nbformat
+import pytest
+from nbparameterise import extract_parameters
+
+from tests.test_xfel_calibrate.conftest import (
+    CalibrateCall,
+    FakeProcessCalibrate,
+    MockProposal,
+)
+
+TEST_CLI_PATH = Path("./notebooks/test/test-cli.ipynb")
+
+
+class TestUserNotebooks:
+    user_nb = None  # Gets set in `self.calibrate_call`
+
+    @pytest.fixture(scope="class", autouse=True)
+    def fake_process_calibrate(self):
+        with FakeProcessCalibrate() as fake_process:
+            yield fake_process
+
+    @pytest.fixture(scope="class")
+    def mock_proposal(self, tmpdir_factory):
+        return MockProposal(
+            tmp_path=Path(tmpdir_factory.mktemp("exp")),
+            instrument="AGIPD",
+            cycle="202031",
+            proposal="p900113",
+            runs=1,
+            sequences=1,
+        )
+
+    @pytest.fixture(scope="function")
+    def calibrate_call(self, mock_proposal: MockProposal, capsys, tmp_path: Path):
+        # The user notebook path template for `TEST`/`TEST-USER-NB` is:
+        #    `"/{instrument}/{cycle}/p{proposal}/test-cli.ipynb"`
+        #
+        # So by setting `instrument` to tmp_path/TEST we set the user notebook
+        # location to one in our tmp dir
+
+        self.user_nb = mock_proposal.path_usr / "test-cli.ipynb"
+
+        shutil.copy(TEST_CLI_PATH.absolute(), str(self.user_nb))
+
+        return CalibrateCall(
+            tmp_path,
+            capsys,
+            in_folder=mock_proposal.path_raw,
+            out_folder=mock_proposal.path_proc,
+            detector="TEST",
+            cal_type="TEST-USER-NB",
+            extra_args=[
+                "--root",
+                str(mock_proposal.path_usr),
+                "--number",
+                "10",
+            ],
+        )
+
+    def test_user_notebook_is_test_notebook(self, calibrate_call: CalibrateCall):
+        assert TEST_CLI_PATH.read_bytes() == self.user_nb.read_bytes()
+
+    def test_output_ipynb(self, calibrate_call: CalibrateCall):
+        notebook_path = calibrate_call.paths.notebooks
+        assert len(notebook_path) == 1
+
+        with notebook_path[0].open() as file:
+            notebook = nbformat.read(file, as_version=4)
+
+        parameters = {p.name: p.value for p in extract_parameters(notebook)}
+
+        assert parameters["number"] == 10
+
+
+class TestUserVenv:
+    @pytest.fixture(scope="class", autouse=True)
+    def fake_process_calibrate(self):
+        with FakeProcessCalibrate() as fake_process:
+            yield fake_process
+
+    @pytest.fixture(scope="class")
+    def mock_proposal(self, tmpdir_factory):
+        return MockProposal(
+            tmp_path=Path(tmpdir_factory.mktemp("exp")),
+            instrument="AGIPD",
+            cycle="202031",
+            proposal="p900113",
+            runs=1,
+            sequences=1,
+        )
+
+    @pytest.fixture(scope="function")
+    def calibrate_call(self, mock_proposal: MockProposal, capsys, tmp_path: Path):
+        self.user_nb = mock_proposal.path_usr / "test-cli.ipynb"
+
+        shutil.copy(TEST_CLI_PATH.absolute(), str(self.user_nb))
+
+        return CalibrateCall(
+            tmp_path,
+            capsys,
+            in_folder=mock_proposal.path_raw,
+            out_folder=mock_proposal.path_proc,
+            detector="TEST",
+            cal_type="TEST-USER-NB-VENV",
+            extra_args=[
+                "--root",
+                str(mock_proposal.path_usr),
+                "--number",
+                "10",
+            ],
+        )
+
+    def test_call(self, calibrate_call: CalibrateCall):
+        assert "Running job in user venv at" in calibrate_call.out
+
+    def test_expected_processes_called(
+        self,
+        mock_proposal: MockProposal,
+        fake_process_calibrate: FakeProcessCalibrate,
+    ):
+        process_calls = [
+            list(shlex.shlex(p, posix=True, punctuation_chars=True))
+            if isinstance(p, str)
+            else p
+            for p in fake_process_calibrate.calls
+        ]
+
+        processes_called = [p[0] for p in process_calls]  # List of the process names
+
+        assert "sbatch" in processes_called
+
+        sbatch_cmd = process_calls[processes_called.index("sbatch")]
+        assert any(str(mock_proposal.path_usr / ".venv") in arg for arg in sbatch_cmd)