Add mock unit tests for Materials Project integration#477
Add mock unit tests for Materials Project integration#477jan-janssen wants to merge 1 commit intomp-apifrom
Conversation
This commit adds `tests/test_materialsproject.py`, which provides unit tests for the `search` and `by_id` functions in `src/structuretoolkit/build/materialsproject.py`. The tests use `unittest.mock` to simulate the `mp_api.client.MPRester` and `structuretoolkit.build.materialsproject.pymatgen_to_ase` functions, ensuring that the integration logic is correctly tested without requiring a real API key or an internet connection. Test cases include: - `test_search`: Verifies that `materialsproject_search` yields processed results and correctly handles the `structure` field. - `test_by_id`: Verifies that `materialsproject_by_id` correctly returns a single structure for `final=True` and a list of structures for `final=False`. The `mp_api` module is mocked in `sys.modules` to avoid import errors in environments where it is not installed. Co-authored-by: jan-janssen <3854739+jan-janssen@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## mp-api #477 +/- ##
==========================================
+ Coverage 81.96% 82.82% +0.86%
==========================================
Files 26 26
Lines 1857 1857
==========================================
+ Hits 1522 1538 +16
+ Misses 335 319 -16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds unit tests to validate the structuretoolkit.build Materials Project integration without requiring a real Materials Project API key or network access by using mocks.
Changes:
- Introduces
tests/test_materialsproject.pywith mocked tests formaterialsproject_searchandmaterialsproject_by_id. - Adds a
sys.modules-based stub formp_apito allow tests to run when the optional dependency is not installed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| # Mock mp_api before importing structuretoolkit | ||
| mock_mp_api = MagicMock() | ||
| sys.modules["mp_api"] = mock_mp_api | ||
| sys.modules["mp_api.client"] = mock_mp_api.client | ||
|
|
||
| import structuretoolkit as stk | ||
|
|
||
| class TestMaterialsProject(unittest.TestCase): |
There was a problem hiding this comment.
The test module permanently overwrites sys.modules["mp_api"] / sys.modules["mp_api.client"] at import time and never restores them. This can leak into other tests in the same process and also masks behavior when the real mp_api package is installed. Consider scoping this to the tests (e.g., patch.dict(sys.modules, ...) in setUp/tearDown or a context manager) and restoring the original sys.modules entries afterward.
| # Mock mp_api before importing structuretoolkit | |
| mock_mp_api = MagicMock() | |
| sys.modules["mp_api"] = mock_mp_api | |
| sys.modules["mp_api.client"] = mock_mp_api.client | |
| import structuretoolkit as stk | |
| class TestMaterialsProject(unittest.TestCase): | |
| import importlib | |
| import types | |
| stk = None | |
| class TestMaterialsProject(unittest.TestCase): | |
| @classmethod | |
| def setUpClass(cls): | |
| global stk | |
| mock_mp_api = types.ModuleType("mp_api") | |
| mock_mp_api.client = types.ModuleType("mp_api.client") | |
| mock_mp_api.client.MPRester = MagicMock() | |
| cls._sys_modules_patcher = patch.dict( | |
| sys.modules, | |
| {"mp_api": mock_mp_api, "mp_api.client": mock_mp_api.client}, | |
| ) | |
| cls._sys_modules_patcher.start() | |
| if "structuretoolkit" in sys.modules: | |
| stk = importlib.reload(sys.modules["structuretoolkit"]) | |
| else: | |
| stk = importlib.import_module("structuretoolkit") | |
| @classmethod | |
| def tearDownClass(cls): | |
| cls._sys_modules_patcher.stop() |
| @patch("mp_api.client.MPRester") | ||
| @patch("structuretoolkit.build.materialsproject.pymatgen_to_ase") | ||
| def test_search(self, mock_pymatgen_to_ase, mock_mp_rester): | ||
| # Setup mock for MPRester as a context manager | ||
| mock_mpr = MagicMock() | ||
| mock_mp_rester.return_value.__enter__.return_value = mock_mpr | ||
|
|
||
| # Setup mock for summary.search | ||
| mock_mpr.summary.search.return_value = [ | ||
| {"material_id": "mp-1", "structure": "mock_pmg_struct"} | ||
| ] | ||
|
|
||
| # Setup mock for pymatgen_to_ase | ||
| mock_pymatgen_to_ase.return_value = "mock_ase_struct" | ||
|
|
||
| # Call search | ||
| results = list(stk.build.materialsproject_search("Fe")) | ||
|
|
||
| # Assertions | ||
| self.assertEqual(len(results), 1) | ||
| self.assertEqual(results[0]["material_id"], "mp-1") | ||
| self.assertEqual(results[0]["structure"], "mock_ase_struct") | ||
| mock_mpr.summary.search.assert_called_once() | ||
| mock_pymatgen_to_ase.assert_called_once_with("mock_pmg_struct") |
There was a problem hiding this comment.
These tests only assert that summary.search() / get_structure_by_material_id() were called, but don’t verify the arguments passed (e.g., chemsys, fields including structure/material_id, or that MPRester is constructed with use_document_model=False / include_user_agent=True). Without asserting call parameters, the tests can pass even if the integration sends an incorrect query or omits required options. Add assertions on the relevant call arguments to make the tests actually guard the intended API interaction contract.
| @patch("mp_api.client.MPRester") | ||
| @patch("structuretoolkit.build.materialsproject.pymatgen_to_ase") | ||
| def test_by_id(self, mock_pymatgen_to_ase, mock_mp_rester): | ||
| # Setup mock for MPRester as a context manager | ||
| mock_mpr = MagicMock() | ||
| mock_mp_rester.return_value.__enter__.return_value = mock_mpr | ||
|
|
||
| # Setup mock for pymatgen_to_ase | ||
| mock_pymatgen_to_ase.side_effect = lambda x: f"ase_{x}" | ||
|
|
||
| # Test final=True | ||
| mock_mpr.get_structure_by_material_id.return_value = "pmg_struct" | ||
| res = stk.build.materialsproject_by_id("mp-1", final=True) | ||
| self.assertEqual(res, "ase_pmg_struct") | ||
| mock_mpr.get_structure_by_material_id.assert_called_with( | ||
| material_id="mp-1", final=True, conventional_unit_cell=False | ||
| ) | ||
|
|
||
| # Test final=False | ||
| mock_mpr.get_structure_by_material_id.return_value = ["pmg_1", "pmg_2"] | ||
| res = stk.build.materialsproject_by_id("mp-1", final=False) | ||
| self.assertEqual(res, ["ase_pmg_1", "ase_pmg_2"]) | ||
| mock_mpr.get_structure_by_material_id.assert_called_with( | ||
| material_id="mp-1", final=False, conventional_unit_cell=False | ||
| ) | ||
|
|
There was a problem hiding this comment.
test_by_id similarly doesn’t assert that MPRester is instantiated with the expected kwargs (notably include_user_agent=True, and optionally api_key when provided). Adding an assertion on the MPRester(...) constructor call would better protect against regressions in how the client is configured.
|
@jan-janssen I didn't see you had already opened this PR in the morning, so I pushed some tests already to #91 I think claude came up with more comprehensive test cases anyway. |
I've added mock unit tests for the Materials Project integration in
structuretoolkit. These tests cover thesearchandby_idfunctions, ensuring they correctly interact with the Materials Project API (via mocks) and process the returned data. By using mocks, we avoid the need for an actual API key and internet connectivity during testing. I've also included a mechanism to mock themp_apidependency if it's missing in the test environment.PR created automatically by Jules for task 1184161835048489774 started by @jan-janssen