11---
2- description:
3- globs: test/*.py
4- alwaysApply: false
2+ globs: test/**/*.py
3+ description: Pytest Unit Test Rules for this repository
54---
6- # Pytest Unit Test Rules
75
8- ## Framework Requirements
9- - **MANDATORY: Use pytest exclusively** - Do not use unittest framework
10- - All tests must be written using pytest syntax and features
11- - Use pytest fixtures instead of unittest setUp/tearDown methods
12- - Use pytest assertions instead of unittest assert methods
6+ ## Pytest Unit Test Rules (Concise)
137
14- ## File Naming Conventions
15- - Test files must start with `test_`
16- - Test class names start with `Test`, test method names start with `test_`
17- - File names should reflect the module or functionality being tested, e.g., `test_user_service.py`
8+ ### Framework
9+ - Use pytest exclusively; prefer fixtures; use pytest `assert` statements.
1810
19- ## File Structure Standards
11+ ### Naming
12+ - Files `test_*`; classes `Test*`; functions `test_*`.
2013
21- ### Directory Organization
22- ```
23- test/
24- ├── backend/ # Backend service tests
25- │ ├── apps/ # Application layer tests
26- │ │ ├── test_app_layer.py
27- │ │ ├── test_app_layer_contract.py
28- │ │ ├── test_app_layer_validation.py
29- │ │ └── test_app_layer_errors.py
30- │ ├── services/ # Service layer tests
31- │ │ ├── test_user_service.py
32- │ │ └── test_auth_service.py
33- │ └── database/ # Database layer tests
34- │ ├── test_models.py
35- │ └── test_crud.py
36- ├── sdk/ # SDK tests
37- │ ├── test_embedding_models.py
38- │ └── test_client.py
39- ├── web_test/ # Web application tests
40- ├── assets/ # Test assets and fixtures
41- ├── pytest.ini # Pytest configuration
42- ├── .coveragerc # Coverage configuration
43- └── requirements.txt # Test dependencies
44- ```
14+ ### Imports
15+ - Order: standard library, third‑party, project.
16+ - Import only the unit under test; mock collaborators using `pytest-mock`.
4517
46- ### File Splitting Guidelines
47- - **When a test file exceeds 500 lines or 50 test methods**, split it into multiple files
48- - Split by functionality or feature area, not by test type
49- - Create a dedicated subdirectory for the split test files:
50- ```
51- test/backend/services/
52- ├── test_auth_service.py # Single file for smaller services
53- ├── test_user_service/ # Directory for split user service tests
54- │ ├── __init__.py # Required for Python package
55- │ ├── test_user_service_core.py # Core user operations
56- │ ├── test_user_service_auth.py # User authentication
57- │ ├── test_user_service_permissions.py # User permissions
58- │ └── test_user_service_validation.py # Input validation
59- └── test_order_service/ # Directory for split order service tests
60- ├── __init__.py
61- ├── test_order_service_core.py
62- ├── test_order_service_payment.py
63- └── test_order_service_shipping.py
64- ```
65- - Use consistent naming pattern: `test_<module>_<feature>.py`
66- - Each subdirectory must contain an `__init__.py` file
67- - Maintain logical grouping within the same directory
68- - Keep the original module name as the directory name for clarity
18+ ### Import and Mock Rules
19+ - Do not directly import external interfaces/clients/services into tests to exercise collaborators.
20+ - Patch where the dependency is imported (lookup site) using a fully‑qualified path.
21+ - Use `side_effect` to cover error paths when appropriate.
6922
70- ### Import Organization
7123```python
72- # 1. Standard library imports first
73- import sys
74- import os
75- import types
76- from typing import Any, Dict, List
77-
78- # 2. Third-party library imports
79- import pytest
80- from pytest_mock import MockFixture # Use pytest-mock instead of unittest.mock
81-
82- # 3. Project internal imports (after mocking dependencies if needed)
83- from sdk.nexent.core.models.embedding_model import OpenAICompatibleEmbedding
84- ```
85-
86- ### Test Class Organization
87- - Each test class corresponds to one class or module being tested
88- - Use pytest fixtures instead of `setUp` and `tearDown` methods
89- - Group test methods by functionality with descriptive method names
90-
91- ### Test Method Structure
92- - Each test method tests only one functionality point
93- - Use pytest assertions (`assert` statements)
94- - Test method names should describe the test scenario, e.g., `test_create_user_success`
95-
96- ## Test Content Standards
97-
98- ### Coverage Requirements
99- - Test normal flow and exception flow
100- - Test boundary conditions and error handling
101- - Use `@pytest.mark.parametrize` for parameterized testing
102-
103- ### Mocking Guidelines
104- - Use `pytest-mock` plugin instead of `unittest.mock`
105- - Mock database operations, API calls, and other external services
106- - Use `side_effect` to simulate exception scenarios
107-
108- ### Assertion Standards
109- - Use pytest assertions with clear error messages
110- - Use `assert` statements with descriptive messages: `assert result.status == "success", f"Expected success, got {result.status}"`
111- - Tests should fail fast and provide clear error location
112-
113- ## Code Examples
114-
115- ### Basic Test Structure (pytest-only)
116- ```python
117- import pytest
11824from pytest_mock import MockFixture
25+ from backend.apps.some_app import create_resource
11926
120- # ---
121- # Fixtures
122- # ---
123-
124- @pytest.fixture()
125- def sample_instance():
126- """Return a sample instance with minimal viable attributes for tests."""
127- return SampleClass(
128- param1="value1",
129- param2="value2"
130- )
131-
132- # ---
133- # Tests for method_name
134- # ---
135-
136- @pytest.mark.asyncio
137- async def test_method_success(sample_instance, mocker: MockFixture):
138- """method_name should return expected result when no exception is raised."""
139-
140- expected_result = {"status": "success"}
141- mock_dependency = mocker.patch(
142- "module.path.external_dependency",
143- return_value=expected_result,
144- )
145-
146- result = await sample_instance.method_name()
147-
148- assert result == expected_result
149- mock_dependency.assert_called_once()
150-
151- @pytest.mark.asyncio
152- async def test_method_failure(sample_instance, mocker: MockFixture):
153- """method_name should handle exceptions gracefully."""
154-
27+ def test_create_resource_success(mocker: MockFixture):
15528 mocker.patch(
156- "module.path.external_dependency ",
157- side_effect=Exception("connection error") ,
29+ "backend.services.model_provider_service.ModelProviderService.create ",
30+ return_value={"id": "res-1"} ,
15831 )
159-
160- result = await sample_instance.method_name()
161-
162- assert result is None # or expected error handling
32+ assert create_resource({...})["id"] == "res-1"
16333```
16434
165- ### Complex Mocking Example (pytest-mock)
166- ```python
167- import pytest
168- from pytest_mock import MockFixture
169-
170- def test_complex_mocking(mocker: MockFixture):
171- """Test with complex dependency mocking."""
172- # Mock external modules
173- mock_external_module = mocker.MagicMock()
174- mock_external_module.ExternalClass = mocker.MagicMock()
175-
176- # Mock complex dependencies
177- class DummyExternalClass:
178- def __init__(self, *args, **kwargs):
179- pass
180-
181- def method_needed_by_tests(self, *args, **kwargs):
182- return {}
183-
184- mock_external_module.ExternalClass = DummyExternalClass
185-
186- # Test the actual functionality
187- # ... test implementation
188- ```
189-
190- ### Parameterized Testing
191- ```python
192- @pytest.mark.parametrize("input_value,expected_output", [
193- ("valid_input", {"status": "success"}),
194- ("invalid_input", {"status": "error"}),
195- ("", {"status": "error"}),
196- ])
197- async def test_method_with_different_inputs(sample_instance, input_value, expected_output):
198- """Test method with various input scenarios."""
199- result = await sample_instance.method(input_value)
200- assert result["status"] == expected_output["status"]
201- ```
202-
203- ### Exception Testing
204- ```python
205- @pytest.mark.asyncio
206- async def test_method_raises_exception(sample_instance):
207- """Test that method raises appropriate exception."""
208- with pytest.raises(ValueError, match="Invalid input") as exc_info:
209- await sample_instance.method("invalid_input")
210-
211- assert "Invalid input" in str(exc_info.value)
212- ```
213-
214- ### State Management
215- ```python
216- @pytest.fixture(autouse=True)
217- def reset_state():
218- """Reset global state between tests."""
219- global_state.clear()
220- mock_objects.reset_mock()
221-
222- @pytest.fixture
223- def test_context():
224- """Provide test context with required attributes."""
225- return TestContext(
226- request_id="req-1",
227- tenant_id="tenant-1",
228- user_id="user-1"
229- )
230- ```
231-
232- ## Best Practices
233-
234- ### 1. Test Isolation
235- - Each test should be independent
236- - Use `autouse=True` fixtures for state reset
237- - Mock external dependencies completely
238-
239- ### 2. Async Testing
240- - Use `@pytest.mark.asyncio` for async tests
241- - Use `mocker.patch` for async operations
242- - Use `assert_called_once()` for async assertions
243-
244- ### 3. Mock Design
245- - Create minimal viable mock objects
246- - Use `DummyClass` pattern for complex dependencies
247- - Record method calls for verification
248-
249- ### 4. Test Organization
250- - Group related tests with comment separators
251- - Use descriptive test names
252- - Include docstrings explaining test purpose
253- - Split large test files into logical subdirectories
254-
255- ### 5. Error Handling
256- - Test both success and failure scenarios
257- - Use `pytest.raises` for exception testing
258- - Verify error messages and types with `match` parameter
35+ ### Structure and Size
36+ - Keep files under 500 lines or split by feature; include `__init__.py` in split directories; use `test_<module>_<feature>.py` names.
25937
260- ### 6. File Management
261- - Keep test files under 500 lines or 50 test methods
262- - Split large files by functionality, not by test type
263- - Use consistent naming patterns for split files
264- - Maintain logical grouping within directories
38+ ### Coverage and Async
39+ - Cover success/error flows and boundaries; use `@pytest.mark.parametrize` for variants.
40+ - Use `@pytest.mark.asyncio` for async tests.
26541
266- ## Migration from unittest
267- - Replace `unittest.TestCase` with plain functions and pytest fixtures
268- - Replace `self.assertTrue()` with `assert` statements
269- - Replace `unittest.mock` with `pytest-mock` plugin
270- - Replace `setUp`/`tearDown` with pytest fixtures
271- - Use `pytest.raises()` instead of `self.assertRaises()`
42+ ### Isolation
43+ - Use `autouse=True` fixtures to reset state; fully mock external I/O and APIs.
27244
273- ## Validation Checklist
274- - [ ] All tests use pytest framework exclusively
275- - [ ] No unittest imports or usage
276- - [ ] External dependencies are mocked with pytest-mock
277- - [ ] Tests cover normal and exception flows
278- - [ ] Async tests use proper decorators
279- - [ ] Assertions are specific and descriptive
280- - [ ] Test names clearly describe scenarios
281- - [ ] Fixtures provide necessary test data
282- - [ ] State is properly reset between tests
283- - [ ] Large test files are split into logical subdirectories
45+ ### Checklist
46+ - [ ] pytest only (no unittest)
47+ - [ ] naming conventions followed
48+ - [ ] collaborators mocked with pytest-mock
49+ - [ ] import/mocking rules followed
50+ - [ ] async tests decorated when needed
51+ - [ ] clear, specific assertions
52+ - [ ] adequate coverage (normal and exception paths)
0 commit comments