Skip to main content

Contributing Tests

This guide explains the testing requirements for contributing code to Gofannon.

Pull Request Requirements

Every PR must include:

  1. Tests for all new code
  2. Tests for modified code (if changing behavior)
  3. 95% minimum coverage on changed files
  4. All tests passing (unit + integration)
  5. No lint errors

PR Testing Checklist

Before submitting your PR, verify:

  • I've written unit tests for new functions/components
  • I've written integration tests for new endpoints/features
  • All tests pass locally
  • Coverage is ≥95% on files I modified
  • Tests follow the project's testing patterns
  • Test names clearly describe what they test
  • No tests are skipped or commented out
  • CI/CD checks are passing

Writing Tests for Your Changes

For New Features

  1. Write tests first (TDD approach recommended)
  2. Cover happy path - normal successful execution
  3. Cover edge cases - empty input, null values, boundaries
  4. Cover error cases - validation failures, exceptions
  5. Test interactions - how your feature works with others

For Bug Fixes

  1. Write a failing test that reproduces the bug
  2. Fix the bug
  3. Verify the test passes
  4. Add additional tests for related edge cases

For Refactoring

  1. Ensure existing tests pass before starting
  2. Don't modify test expectations (behavior shouldn't change)
  3. Add tests if coverage decreased
  4. Verify all tests still pass after refactoring

Test Coverage Rules

You Are Responsible For

  • Files you create: 95% minimum coverage
  • Files you modify: Maintain or improve existing coverage
  • New functions/methods: 100% coverage (no exceptions)

Coverage Exceptions

Only exclude from coverage:

  • Type checking blocks (if TYPE_CHECKING:)
  • Abstract methods (@abstractmethod)
  • Main blocks (if __name__ == "__main__":)
  • Explicitly unreachable code (pragma: no cover)

Never exclude:

  • Business logic
  • Error handling
  • Validation code
  • Utility functions

Test Organization

File Naming

# Backend (Python)
tests/unit/test_<module_name>.py
tests/integration/test_<feature_name>.py

# Frontend (JavaScript)
src/components/ComponentName.test.jsx
src/utils/utilityName.test.js

Test Structure

# Backend
class TestFeatureName:
"""Test suite for FeatureName."""

def test_feature_does_something_when_condition(self):
"""Test that feature does X when Y happens."""
# Arrange
# Act
# Assert
// Frontend
describe('ComponentName', () => {
describe('when prop X is true', () => {
it('renders element Y', () => {
// Arrange, Act, Assert
});
});
});

Common Scenarios

Adding a New API Endpoint

Required Tests:

  1. Unit test for the route handler function
  2. Unit tests for any new service methods
  3. Integration test for the full HTTP request/response
  4. Test authentication/authorization
  5. Test validation errors
  6. Test success case with valid data

Example:

# tests/unit/test_routes.py
def test_create_agent_validates_input(mock_db):
with pytest.raises(ValidationError):
create_agent(CreateAgentRequest(name="")) # Empty name

def test_create_agent_saves_to_database(mock_db):
agent = create_agent(CreateAgentRequest(name="Test"))
mock_db.save.assert_called_once()

# tests/integration/test_agent_endpoints.py
def test_create_agent_endpoint(client):
response = client.post("/agents", json={"name": "Test"})
assert response.status_code == 201
assert response.json()["name"] == "Test"

Adding a New React Component

Required Tests:

  1. Test component renders with required props
  2. Test user interactions (clicks, typing, etc.)
  3. Test conditional rendering
  4. Test prop validation/defaults
  5. Test error states
  6. Test accessibility

Example:

// ActionCard.test.jsx
describe('ActionCard', () => {
it('renders with required props', () => {
render(<ActionCard {...requiredProps} />);
expect(screen.getByText('Title')).toBeInTheDocument();
});

it('calls onClick when clicked', async () => {
const onClick = vi.fn();
render(<ActionCard {...requiredProps} onClick={onClick} />);

await userEvent.click(screen.getByRole('button'));

expect(onClick).toHaveBeenCalled();
});

it('shows error message when prop is invalid', () => {
render(<ActionCard {...requiredProps} title="" />);
expect(screen.getByText('Title is required')).toBeInTheDocument();
});
});

Modifying Existing Code

  1. Run existing tests to verify they still pass
  2. Update tests if behavior changed (document why in PR)
  3. Add new tests for new behavior/edge cases
  4. Ensure coverage doesn't decrease

Running Tests Locally

Before Creating PR

# 1. Run all unit tests
cd webapp
pnpm test:unit

# 2. Check coverage
pnpm test:coverage

# 3. Run integration tests if you changed API/services
pnpm test:integration

# 4. Run lint
cd packages/webui
pnpm lint

# 5. Verify everything passes
cd ../..
pnpm test

During PR Review

If CI fails:

  1. Check GitHub Actions logs
  2. Reproduce failure locally
  3. Fix the issue
  4. Re-run tests locally
  5. Push fix

Code Review Focus

Reviewers will check:

  • Tests cover new/modified code
  • Test names are descriptive
  • Tests are independent (no shared state)
  • Appropriate test type (unit vs integration)
  • Mocks used correctly in unit tests
  • Edge cases covered
  • No flaky tests (random failures)
  • Coverage meets 95% threshold

Examples of Good PRs

Example 1: New Feature

Title: Add user allowance reset endpoint

Tests Added:
- test_reset_allowance_sets_to_monthly_limit (unit)
- test_reset_allowance_clears_usage_history (unit)
- test_reset_allowance_endpoint_returns_updated_user (integration)
- test_reset_allowance_requires_authentication (integration)

Coverage: 98% on modified files

Example 2: Bug Fix

Title: Fix user creation with missing email

Tests Added:
- test_create_user_without_email_uses_default (unit)
- test_create_user_with_null_email_raises_error (unit)

Before: Bug allowed null emails
After: Bug fixed, tests verify correct behavior
Coverage: 100% on user_service.py

Getting Help

Test Writing Help

  • Review existing tests in similar files
  • Check the Unit Testing Guide
  • Ask in team chat or PR comments

Coverage Issues

  • Run pnpm test:coverage to see uncovered lines
  • Focus on testing the red lines in coverage report
  • Review Coverage Requirements

CI Failures

  • Check GitHub Actions logs for error details
  • Reproduce locally with same command from CI
  • Check if it's a timing issue (flaky test)

Common Mistakes to Avoid

1. Not Testing Error Cases

# Bad - only tests success
def test_create_user():
user = create_user("test@example.com")
assert user.email == "test@example.com"

# Good - tests error cases too
def test_create_user_with_invalid_email():
with pytest.raises(ValidationError):
create_user("invalid-email")

def test_create_user_with_duplicate_email():
create_user("test@example.com")
with pytest.raises(DuplicateEmailError):
create_user("test@example.com")

2. Testing Implementation Instead of Behavior

// Bad - tests implementation detail
it('calls setState with correct value', () => {
const setState = vi.spyOn(component, 'setState');
component.handleClick();
expect(setState).toHaveBeenCalledWith({ clicked: true });
});

// Good - tests visible behavior
it('shows success message after clicking', async () => {
render(<Component />);
await userEvent.click(screen.getByRole('button'));
expect(screen.getByText('Success!')).toBeInTheDocument();
});

3. Shared State Between Tests

# Bad - shared state
user = User(_id="test-123")

def test_update_email():
user.email = "new@example.com" # Modifies shared object!

def test_update_name():
user.name = "New Name" # Depends on previous test!

# Good - isolated tests
def test_update_email():
user = User(_id="test-123")
user.email = "new@example.com"

def test_update_name():
user = User(_id="test-123")
user.name = "New Name"

4. Skipping Integration Tests

# Bad - only unit tests for new endpoint
def test_create_agent_saves_to_db(mock_db):
create_agent(data, mock_db)
mock_db.save.assert_called()

# Good - also has integration test
def test_create_agent_endpoint_e2e(client):
response = client.post("/agents", json=valid_data)
assert response.status_code == 201

# Verify it's actually in the database
agent = client.get(f"/agents/{response.json()['id']}")
assert agent.json()["name"] == valid_data["name"]

PR Approval Criteria

Your PR will be approved when:

  1. ✅ All CI checks pass
  2. ✅ Coverage is ≥95%
  3. ✅ Tests are well-written and maintainable
  4. ✅ Code review feedback addressed
  5. ✅ Documentation updated (if needed)

Resources