diff --git a/tests/mock_things/__init__.py b/tests/mock_things/__init__.py deleted file mode 100644 index c65a6219..00000000 --- a/tests/mock_things/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""A package of test module providing mock Things for testing. - -The mock things do not subclass the original things to minimise the inflation of -test coverage. -""" diff --git a/tests/mock_things/mock_autofocus.py b/tests/mock_things/mock_autofocus.py deleted file mode 100644 index cf7de4f3..00000000 --- a/tests/mock_things/mock_autofocus.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Testing submodule with mock Autofocus Things. - -These mocks are designed to be inserted as dependencies to provide specific -functionality and return values. - -The mocks do not subclass Things. Instead, they return predefined -answers to functions. -""" - - -class MockAutoFocusThing: - """A mock autofocus Thing that imports no code from AutofocusThing. - - The class needs functionality added to it over time as more complex - mocking is needed. It imports no code from AutofocusThing so that coverage - is not artificially inflated. - """ - - # Counter for checking functions were called - mock_call_count = {"looping_autofocus": 0} - - def looping_autofocus(self, dz: int = 2000, start: str = "centre") -> None: # noqa: ARG002 - """Mock autofocus with no return.""" - self.mock_call_count["looping_autofocus"] += 1 diff --git a/tests/mock_things/mock_camera.py b/tests/mock_things/mock_camera.py deleted file mode 100644 index 2b67ed78..00000000 --- a/tests/mock_things/mock_camera.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Testing submodule with mock Camera Things. - -These mocks are designed to be inserted as dependencies to provide specific -functionality and return values. - -The mocks do not subclass Things. Instead, they return predefined -answers to functions. -""" - -from unittest.mock import Mock, PropertyMock - -from openflexure_microscope_server.background_detect import ( - BackgroundDetectorStatus, - ColourChannelDetectSettings, -) - - -class MockCameraThing(Mock): - """A mock camera Thing that imports no code from ``BaseCamera``. - - The class needs functionality added to it over time as more complex - mocking is needed. It imports no code from ``BaseCamera`` or any other - camera Thing, so that coverage is not artificially inflated. - """ - - def __init__(self, *args, **kwargs): - """Initialise the mock camera, args and kwargs are the mock args and kwargs.""" - super().__init__(*args, **kwargs) - - self.background_detector_status = PropertyMock( - return_value=BackgroundDetectorStatus( - ready=True, - settings=ColourChannelDetectSettings().model_dump(), - settings_schema={"fake": "schema"}, - ) - ) diff --git a/tests/mock_things/mock_csm.py b/tests/mock_things/mock_csm.py deleted file mode 100644 index 4daa0d57..00000000 --- a/tests/mock_things/mock_csm.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Testing submodule with mock Camera Stage Mapping Things. - -These mocks are designed to be inserted as dependencies to provide specific -functionality and return values. - -The mocks do not subclass Things. Instead, they return predefined -answers to functions. -""" - - -class MockCSMThing: - """A mock CSM Thing that imports no code from CameraStageMapper. - - The class needs functionality added to it over time as more complex - mocking is needed. It imports no code from CameraStageMapper so that coverage - is not artificially inflated. - """ - - image_resolution = (123, 456) - image_to_stage_displacement_matrix = [ - [0.03061156624485296, 1.8031242270940833], - [1.773236372778601, 0.006660431608601435], - ] diff --git a/tests/mock_things/mock_stage.py b/tests/mock_things/mock_stage.py deleted file mode 100644 index 6733986a..00000000 --- a/tests/mock_things/mock_stage.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Testing submodule with mocks StageThings. - -These mocks are designed to be inserted as dependencies to provide specific -functionality and return values. - -The mocks do not subclass existing Things. Instead, they return predefined -answers to functions. -""" - - -class MockStageThing: - """A mock Thing for a stage that imports no code from BaseStage. - - The class needs functionality added to it over time as more complex - mocking is needed. It imports no code from BaseStage so that coverage - is not artificially inflated. - """ - - position = {"x": 3635, "y": 10, "z": 617} diff --git a/tests/test_smart_scan.py b/tests/test_smart_scan.py index 15cd5789..4a59e47e 100644 --- a/tests/test_smart_scan.py +++ b/tests/test_smart_scan.py @@ -35,11 +35,6 @@ from openflexure_microscope_server.things.smart_scan import ( SmartScanThing, ) -from .mock_things.mock_autofocus import MockAutoFocusThing -from .mock_things.mock_camera import MockCameraThing -from .mock_things.mock_csm import MockCSMThing -from .mock_things.mock_stage import MockStageThing - # Use our own dir in the root temp dir not a dynamically generated one so we # have some control of when it is deleted SCAN_DIR = os.path.join(tempfile.gettempdir(), "scans") @@ -54,7 +49,9 @@ def _clear_scan_dir() -> None: @pytest.fixture def smart_scan_thing(): """Return a smart scan thing as a fixture.""" - return create_thing_without_server(SmartScanThing, scans_folder=SCAN_DIR) + return create_thing_without_server( + SmartScanThing, scans_folder=SCAN_DIR, mock_all_slots=True + ) def test_initial_properties(smart_scan_thing): @@ -156,7 +153,9 @@ def test_delete_all_scans(smart_scan_thing, caplog): assert len(caplog.records) == 0 -def _run_only_outer_scan(adjust_initial_state: Optional[Callable] = None): +def _run_only_outer_scan( + smart_scan_thing, mocker, adjust_initial_state: Optional[Callable] = None +): """Create a subclass of SmartScanThing to mock _run_scan and run sample_scan. This should do all the set up for a scan, move into the mocked @@ -170,69 +169,39 @@ def _run_only_outer_scan(adjust_initial_state: Optional[Callable] = None): This seems hard to do with a fixture so it is being done with a private function """ - # cancel handle shouldn't be used. Set to arbitrary value for checking - af_mock = MockAutoFocusThing() - stage_mock = MockStageThing() - cam_mock = MockCameraThing() - csm_mock = MockCSMThing() - class MockedSmartScanThing(SmartScanThing): - """Mocked version of SmartScanThing with a patched _run_scan method.""" + def check_locked(*_args, **_kwargs): + """Check the scan is locked.""" + assert smart_scan_thing._scan_lock.locked() - # Counter for checking functions were called - mock_call_count = {"_run_scan": 0} - - def _run_scan(self): - self.mock_call_count["_run_scan"] += 1 - - """Check scan vars are set up as expected""" - assert not self._scan_lock.acquire(timeout=0.1) - assert self._autofocus is af_mock - assert self._stage is stage_mock - assert self._cam is cam_mock - assert self._csm is csm_mock - - # mock smart scan thing - mock_ss_thing = create_thing_without_server( - MockedSmartScanThing, scans_folder=SCAN_DIR - ) + mocker.patch.object(smart_scan_thing, "_run_scan", side_effect=check_locked) if adjust_initial_state is not None: - adjust_initial_state(mock_ss_thing) + adjust_initial_state(smart_scan_thing) exec_info = None try: - mock_ss_thing.sample_scan( - autofocus=af_mock, - stage=stage_mock, - cam=cam_mock, - csm=csm_mock, - scan_name="FooBar", - ) + smart_scan_thing.sample_scan(scan_name="FooBar") + except Exception as e: exec_info = e - assert mock_ss_thing._scan_lock.acquire(timeout=0.1) - mock_ss_thing._scan_lock.release() - assert mock_ss_thing._autofocus is None - assert mock_ss_thing._stage is None - assert mock_ss_thing._cam is None - assert mock_ss_thing._csm is None + assert not smart_scan_thing._scan_lock.locked() # Return the mock thing for further state testing, and the # exec_info of any uncaught exceptions that were raised - return mock_ss_thing, exec_info + return smart_scan_thing, exec_info -def test_outer_scan(): +def test_outer_scan(smart_scan_thing, mocker): """Test setup and teardown of the scan.""" - mock_ss_thing, exec_info = _run_only_outer_scan() + mock_ss_thing, exec_info = _run_only_outer_scan(smart_scan_thing, mocker) assert exec_info is None # Checked the mocked _run_scan was run exactly once - assert mock_ss_thing.mock_call_count["_run_scan"] == 1 + assert mock_ss_thing._run_scan.call_count == 1 -def test_outer_scan_wo_sample_skip(): +def test_outer_scan_wo_sample_skip(smart_scan_thing, mocker): """Test setup and teardown of the scan.""" def _set_skip_background(mock_ss_thing): @@ -240,11 +209,13 @@ def test_outer_scan_wo_sample_skip(): # __dict__ to avoid triggering property emits that require a server mock_ss_thing.__dict__["skip_background"] = False - mock_ss_thing, exec_info = _run_only_outer_scan(_set_skip_background) + mock_ss_thing, exec_info = _run_only_outer_scan( + smart_scan_thing, mocker, _set_skip_background + ) assert exec_info is None # Checked the mocked _run_scan was run exactly once - assert mock_ss_thing.mock_call_count["_run_scan"] == 1 + assert mock_ss_thing._run_scan.call_count == 1 MOCK_SCAN_NAME = "test_name_0001" @@ -281,17 +252,14 @@ def scan_thing_mocked_for_scan_data(smart_scan_thing, mocker): "_calc_displacement_from_test_image", return_value=[100, 100], ) + + smart_scan_thing._stage.position = MOCK_START_POS + mock_ongoing_scan = mocker.Mock() mock_ongoing_scan.name = MOCK_SCAN_NAME mock_ongoing_scan.images_dir = MOCK_SCAN_DIR - mock_stage = mocker.Mock() - mock_stage.position = MOCK_START_POS - - mock_autofocus = mocker.Mock() - smart_scan_thing._ongoing_scan = mock_ongoing_scan - smart_scan_thing._stage = mock_stage - smart_scan_thing._autofocus = mock_autofocus + yield smart_scan_thing @@ -333,7 +301,6 @@ def scan_thing_mocked_for_run_scan(scan_thing_mocked_for_scan_data, mocker): Mocks and _cam is a MockCameraThing """ scan_thing = scan_thing_mocked_for_scan_data - scan_thing._cam = MockCameraThing() mocker.patch.object(scan_thing, "_main_scan_loop") mocker.patch.object(scan_thing, "_return_to_starting_position") mocker.patch.object(scan_thing, "_perform_final_stitch")