Writing Tests
Python tests (pytest)
Test structure
tests/├── conftest.py ← shared fixtures└── orchid/ ├── migrations/ ← migration-specific tests ├── models/ ← model tests └── utils/ ← utility function tests ├── test_email_preview.py ├── test_feature_flags.py ├── test_forms.py ├── test_html_sanitizer.py └── test_recaptcha.pyAvailable fixtures (from tests/conftest.py)
@pytest.fixturedef app(): """Flask app with SQLite in-memory DB, CSRF disabled.""" # Creates all tables, yields app, drops all tables
@pytest.fixturedef client(app): """Test client — use for HTTP request testing."""
@pytest.fixturedef runner(app): """CLI runner — use for testing Flask CLI commands."""
@pytest.fixturedef app_context(app): """Application context — use when you need DB access without HTTP."""Writing a unit test
import pytestfrom orchid.utils.my_utility import my_function
class TestMyFunction: def test_basic_case(self): result = my_function('input') assert result == 'expected output'
def test_edge_case_empty_string(self): result = my_function('') assert result is None
def test_raises_on_invalid_input(self): with pytest.raises(ValueError): my_function(None)Writing a model test
import pytestfrom orchid import models
class TestMyModel: def test_create_record(self, app_context): from orchid.utils.flaskutils import Crudler record = models.MyModel( name='Test', case_id=None # or create a case first ) Crudler.add(record) Crudler.commit()
fetched = models.MyModel.query.filter_by(name='Test').first() assert fetched is not None assert fetched.name == 'Test'Writing an integration test (HTTP)
import pytest
class TestMyView: def test_unauthenticated_redirects(self, client): response = client.get('/admin/my-page') assert response.status_code in (302, 401, 403)
def test_authenticated_returns_200(self, client, app_context): # Log in first with client.session_transaction() as sess: sess['logged_in'] = True sess['user_type'] = 'admin' sess['admin_id'] = 1 # use a seeded admin ID
response = client.get('/admin/my-page') assert response.status_code == 200Running tests
pytest # all testspytest tests/orchid/utils/ # all utils testspytest tests/orchid/utils/test_forms.py # single filepytest -m unit # unit tests onlypytest -m integration # integration tests onlypytest -v # verbose outputpytest --tb=short # shorter tracebacksTest markers
Use markers to categorize tests:
import pytest
@pytest.mark.unitdef test_something(): pass
@pytest.mark.integrationdef test_something_else(app_context): passMarkers are registered in pytest.ini. Run specific markers with -m unit or -m integration.
JavaScript tests (Jest)
Test structure
tests/js/├── setup.js ← Jest setup file (jsdom config)└── **/*.test.js ← test files (mirror static/js structure)Writing a Jest test
import { myFunction } from '../../../static/js/utils/my_utility.mjs';
describe('myFunction', () => { test('returns expected output for basic input', () => { expect(myFunction('input')).toBe('expected output'); });
test('handles empty string', () => { expect(myFunction('')).toBeNull(); });
test('throws on null input', () => { expect(() => myFunction(null)).toThrow(); });});Running JS tests
npm test # run allnpm run test:watch # watch modenpm run test:coverage # with coverage reportCoverage
JS coverage is collected from static/js/**/*.{js,mjs} (excluding .min.js and vendor/). Run npm run test:coverage to see which modules have low coverage.
What to test (prioritization)
Given that the test suite is thin, prioritize in this order:
- Business logic utility functions — anything in
orchid/utils/that has no side effects is easy to unit test and high value - Jinja filters — test that filters produce the right output for edge cases (null inputs, unexpected formats)
- Model validation — test that models enforce the constraints you care about
- API endpoints — integration tests that verify the right HTTP status codes and response shapes
- View endpoints — lower priority (mostly just check they return 200 and don’t crash)