Testing¶
Testing strategies for Odoo modules and Field PWA.
Odoo Module Testing¶
Test File Structure¶
tests/__init__.py:
Writing Tests¶
tests/test_my_model.py:
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase, tagged
from odoo.exceptions import ValidationError
@tagged('post_install', '-at_install')
class TestMyModel(TransactionCase):
@classmethod
def setUpClass(cls):
"""Set up test data."""
super().setUpClass()
cls.partner = cls.env['res.partner'].create({
'name': 'Test Partner',
'email': 'test@example.com',
})
cls.my_model = cls.env['my.model'].create({
'name': 'Test Record',
'partner_id': cls.partner.id,
})
def test_create_record(self):
"""Test record creation."""
record = self.env['my.model'].create({
'name': 'New Record',
})
self.assertTrue(record.id)
self.assertEqual(record.state, 'draft')
def test_action_confirm(self):
"""Test confirm action changes state."""
self.assertEqual(self.my_model.state, 'draft')
self.my_model.action_confirm()
self.assertEqual(self.my_model.state, 'confirmed')
def test_action_done(self):
"""Test done action after confirm."""
self.my_model.action_confirm()
self.my_model.action_done()
self.assertEqual(self.my_model.state, 'done')
def test_validation_error(self):
"""Test validation raises error."""
with self.assertRaises(ValidationError):
self.env['my.model'].create({
'name': '', # Required field
})
def test_compute_field(self):
"""Test computed field calculation."""
# Add test for computed fields
self.my_model._compute_total()
self.assertGreaterEqual(self.my_model.total_amount, 0)
Running Tests¶
# Run all tests for a module
docker compose exec odoo odoo \
--test-enable \
-i my_module \
--stop-after-init \
-d odoo_test
# Run specific test class
docker compose exec odoo odoo \
--test-enable \
--test-tags my_module \
-i my_module \
--stop-after-init \
-d odoo_test
# Run with verbose output
docker compose exec odoo odoo \
--test-enable \
-i my_module \
--stop-after-init \
--log-level=test \
-d odoo_test
Test Tags¶
@tagged('post_install', '-at_install') # Run after install
@tagged('at_install') # Run during install
@tagged('-standard', 'slow') # Custom tags
Common tags:
| Tag | Description |
|-----|-------------|
| post_install | Run after module installation |
| at_install | Run during module installation |
| -at_install | Skip during installation |
| standard | Standard test suite |
| -standard | Exclude from standard suite |
Test Types¶
TransactionCase¶
Each test runs in a transaction that's rolled back. Fast, isolated.
from odoo.tests import TransactionCase
class TestFast(TransactionCase):
def test_something(self):
# Changes are rolled back after test
pass
SavepointCase¶
Uses savepoints instead of full rollback. Good for tests that need commits.
from odoo.tests import SavepointCase
class TestSavepoint(SavepointCase):
def test_something(self):
# Uses savepoints for partial rollback
pass
HttpCase¶
For testing HTTP endpoints and web controllers.
from odoo.tests import HttpCase
class TestHttp(HttpCase):
def test_web_page(self):
response = self.url_open('/my/endpoint')
self.assertEqual(response.status_code, 200)
PWA Testing¶
Test Structure¶
pwa/
└── tests/
├── __init__.py
├── conftest.py # Pytest fixtures
├── test_auth.py
├── test_jobs.py
└── test_api.py
Pytest Configuration¶
conftest.py:
import pytest
from app import create_app
@pytest.fixture
def app():
"""Create test application."""
app = create_app('testing')
app.config['TESTING'] = True
return app
@pytest.fixture
def client(app):
"""Create test client."""
return app.test_client()
@pytest.fixture
def authenticated_client(client):
"""Create authenticated test client."""
client.post('/login', data={
'username': 'test_user',
'password': 'test_pass'
})
return client
Writing PWA Tests¶
test_auth.py:
def test_login_page(client):
"""Test login page loads."""
response = client.get('/login')
assert response.status_code == 200
assert b'Login' in response.data
def test_login_success(client, mocker):
"""Test successful login."""
mocker.patch('services.odoo.authenticate', return_value={'uid': 1})
response = client.post('/login', data={
'username': 'admin',
'password': 'admin'
}, follow_redirects=True)
assert response.status_code == 200
def test_login_failure(client, mocker):
"""Test failed login."""
mocker.patch('services.odoo.authenticate', return_value=None)
response = client.post('/login', data={
'username': 'wrong',
'password': 'wrong'
})
assert b'Invalid credentials' in response.data
def test_logout(authenticated_client):
"""Test logout."""
response = authenticated_client.get('/logout', follow_redirects=True)
assert response.status_code == 200
test_api.py:
import json
def test_api_health(client):
"""Test health endpoint."""
response = client.get('/api/health')
assert response.status_code == 200
data = json.loads(response.data)
assert data['status'] in ['healthy', 'degraded']
def test_api_jobs_unauthenticated(client):
"""Test jobs API requires auth."""
response = client.get('/api/jobs')
assert response.status_code == 401
def test_api_jobs_authenticated(authenticated_client, mocker):
"""Test jobs API returns data."""
mocker.patch('services.odoo.get_jobs', return_value=[
{'id': 1, 'name': 'Test Job'}
])
response = authenticated_client.get('/api/jobs')
assert response.status_code == 200
data = json.loads(response.data)
assert len(data['jobs']) == 1
Running PWA Tests¶
# Run all tests
docker compose exec pwa python -m pytest
# Run with coverage
docker compose exec pwa python -m pytest --cov=app --cov-report=html
# Run specific file
docker compose exec pwa python -m pytest tests/test_auth.py
# Run with verbose output
docker compose exec pwa python -m pytest -v
# Run specific test
docker compose exec pwa python -m pytest tests/test_auth.py::test_login_success
Manual Testing Checklist¶
Odoo Module¶
- Module installs without errors
- All menu items appear correctly
- Forms load and save properly
- Search and filters work
- Actions/buttons function correctly
- Security rules enforced (test as different users)
- Reports generate correctly
- No JavaScript console errors
PWA¶
- Login/logout works
- Dashboard loads
- All pages accessible
- Forms submit correctly
- Offline mode works
- Data syncs when online
- Mobile responsive
- No console errors
Integration¶
- PWA connects to Odoo API
- Data flows correctly between systems
- Signatures upload to S3
- SMS sends via JustCall
Test Coverage¶
Odoo Coverage¶
# Install coverage
pip install coverage
# Run with coverage
coverage run --source=extra-addons/odoo/my_module \
odoo-bin --test-enable -i my_module --stop-after-init
# Generate report
coverage report
coverage html
PWA Coverage¶
docker compose exec pwa python -m pytest \
--cov=app \
--cov-report=term-missing \
--cov-report=html
CI/CD Integration¶
GitHub Actions Example¶
name: Tests
on: [push, pull_request]
jobs:
test-odoo:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: odoo
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Run Odoo Tests
run: |
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
docker compose exec -T odoo odoo --test-enable -i my_module --stop-after-init
test-pwa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r pwa/requirements.txt pytest pytest-cov
- name: Run tests
run: cd pwa && pytest --cov=app
Debugging Failed Tests¶
Odoo¶
# Run with debug logging
docker compose exec odoo odoo \
--test-enable \
-i my_module \
--stop-after-init \
--log-level=debug \
-d odoo_test 2>&1 | tee test.log
# Check log for errors
grep -i error test.log