Files
Fran Jurmanović b9cb315944 add tests
2025-07-06 19:19:42 +02:00
..
2025-07-06 19:19:42 +02:00
2025-07-06 19:19:42 +02:00
2025-07-06 19:19:42 +02:00
2025-07-06 19:19:42 +02:00
2025-07-06 19:19:42 +02:00

Testing Module

This directory contains the comprehensive testing framework for the Omega server application. The testing module is organized to support unit tests, integration tests, GraphQL tests, and provides utilities for creating test fixtures and managing test data.

Directory Structure

tests/
├── README.md                 # This file
├── testing.go                # Main testing utilities and test suite
├── fixtures/                 # Test data fixtures
│   └── fixtures.go           # Predefined test data
├── unit/                     # Unit testing utilities
│   └── unit_test_utils.go    # Unit test helpers and assertions
├── integration/              # Integration testing utilities
│   └── integration_test_utils.go  # HTTP request testing and API integration
└── graphql/                  # GraphQL testing utilities
    └── graphql_test_utils.go # GraphQL query execution and testing

Getting Started

Prerequisites

Before running tests, ensure you have:

  1. PostgreSQL Test Database: Set up a dedicated test database
  2. Environment Variables: Configure test-specific environment variables
  3. Go Testing Tools: Standard Go testing framework

Environment Configuration

Set the following environment variables for testing:

# Test Database Configuration
TEST_DB_HOST=localhost
TEST_DB_PORT=5432
TEST_DB_USER=postgres
TEST_DB_PASSWORD=password
TEST_DB_NAME=omega_test
TEST_DB_SSL_MODE=disable

# Optional: Test-specific configurations
LOG_LEVEL=DEBUG
DEFAULT_ADMIN_PASSWORD=testpassword123

Running Tests

# Run all tests
go test ./...

# Run tests with verbose output
go test -v ./...

# Run specific test package
go test ./tests/unit/...
go test ./tests/integration/...
go test ./tests/graphql/...

# Run tests with coverage
go test -cover ./...

# Run tests with detailed coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Test Categories

1. Unit Tests (tests/unit/)

Unit tests focus on testing individual components in isolation:

  • Model validation: Testing model methods and validation logic
  • Service layer: Testing business logic without external dependencies
  • Utility functions: Testing helper functions and utilities
  • Repository layer: Testing data access logic with mocked dependencies

Example Unit Test Structure:

func TestUserValidation(t *testing.T) {
    utils := unit.NewUnitTestUtils(nil)
    
    user := utils.MockUser("test-id", "test@example.com", "Test User")
    
    err := user.Validate()
    utils.AssertNoError(t, err)
    utils.AssertStringEqual(t, "test@example.com", user.Email)
}

2. Integration Tests (tests/integration/)

Integration tests verify the interaction between different components:

  • API endpoints: Testing complete HTTP request/response cycles
  • Database operations: Testing real database interactions
  • Authentication flows: Testing JWT token generation and validation
  • Business workflows: Testing complete user scenarios

Example Integration Test Structure:

func TestCreateUserAPI(t *testing.T) {
    testSuite := tests.NewTestSuite()
    testSuite.Setup(t)
    defer testSuite.Teardown(t)
    
    utils := integration.NewIntegrationTestUtils(app, testSuite.DB, membershipService)
    
    req := integration.TestRequest{
        Method: "POST",
        URL:    "/v1/users",
        Body: map[string]interface{}{
            "email": "new@example.com",
            "fullName": "New User",
            "password": "password123",
        },
    }
    
    resp := utils.ExecuteRequest(t, req)
    utils.AssertStatusCode(t, resp, 201)
}

3. GraphQL Tests (tests/graphql/)

GraphQL tests specifically target the GraphQL API layer:

  • Query execution: Testing GraphQL queries and mutations
  • Schema validation: Ensuring GraphQL schema correctness
  • Error handling: Testing GraphQL error responses
  • Authentication: Testing GraphQL with JWT authentication

Example GraphQL Test Structure:

func TestGraphQLLogin(t *testing.T) {
    testSuite := tests.NewTestSuite()
    testSuite.Setup(t)
    defer testSuite.Teardown(t)
    
    utils := graphql.NewGraphQLTestUtils(testSuite.DB, membershipService)
    
    response := utils.ExecuteLoginMutation(t, "admin@example.com", "password123")
    
    utils.AssertNoErrors(t, response)
    utils.AssertDataNotNil(t, response)
    
    token := utils.ExtractString(t, response, "login", "token")
    utils.AssertNotEmpty(t, token)
}

Test Utilities

TestSuite (testing.go)

The main test suite provides:

  • Database Setup: Automatic test database configuration
  • Migration Management: Running migrations for tests
  • Cleanup: Automatic cleanup after tests
  • Dependency Injection: DI container setup for tests
  • Helper Methods: Common test operations
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)

// Create test data
user := testSuite.CreateTestUser(t, "test@example.com", "Test User")
project := testSuite.CreateTestProject(t, "Test Project", "Description", user.ID, typeID)

Fixtures (fixtures/)

Predefined test data for consistent testing:

  • User Fixtures: Standard test users with different roles
  • Project Fixtures: Sample projects with various configurations
  • Task Fixtures: Tasks in different states and priorities
  • Integration Fixtures: Sample third-party integrations
  • GraphQL Queries: Common GraphQL operations
fixtures := fixtures.NewFixtures()
users := fixtures.Users()
adminUser := users["admin"]
testProject := fixtures.Projects()["omega_project"]

Assertions

Each test utility provides rich assertion methods:

  • Model Assertions: Compare model instances
  • Response Assertions: Validate HTTP responses
  • GraphQL Assertions: Check GraphQL responses
  • Database Assertions: Verify database state

Best Practices

1. Test Organization

  • One test file per source file: Mirror the source code structure
  • Descriptive test names: Use TestFunctionName_Scenario_ExpectedBehavior format
  • Group related tests: Use subtests with t.Run() for related scenarios

2. Test Data Management

  • Use fixtures: Leverage predefined fixtures for consistent test data
  • Clean up: Always clean up test data after tests
  • Isolation: Ensure tests don't depend on each other
  • Transactions: Use database transactions for rollback capabilities

3. Mocking and Dependencies

  • Mock external services: Don't make real API calls in tests
  • Inject dependencies: Use dependency injection for testability
  • Test doubles: Use appropriate test doubles (mocks, stubs, fakes)

4. Error Testing

  • Test error paths: Ensure error conditions are properly tested
  • Validate error messages: Check that error messages are meaningful
  • Edge cases: Test boundary conditions and edge cases

5. Performance Considerations

  • Fast tests: Keep unit tests fast (< 100ms each)
  • Parallel execution: Use t.Parallel() where appropriate
  • Database optimization: Use transactions and minimal data for speed

Test Patterns

1. Table-Driven Tests

func TestUserValidation(t *testing.T) {
    testCases := []struct {
        name        string
        email       string
        expectedErr string
    }{
        {"valid email", "test@example.com", ""},
        {"invalid email", "invalid-email", "invalid email format"},
        {"empty email", "", "email is required"},
    }
    
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            user := &model.User{Email: tc.email}
            err := user.Validate()
            
            if tc.expectedErr == "" {
                assert.NoError(t, err)
            } else {
                assert.EqualError(t, err, tc.expectedErr)
            }
        })
    }
}

2. Setup and Teardown

func TestUserService(t *testing.T) {
    testSuite := tests.NewTestSuite()
    testSuite.Setup(t)
    defer testSuite.Teardown(t)
    
    t.Run("CreateUser", func(t *testing.T) {
        // Test implementation
    })
    
    t.Run("GetUser", func(t *testing.T) {
        // Test implementation
    })
}

3. Database Transactions

func TestDatabaseOperations(t *testing.T) {
    testSuite := tests.NewTestSuite()
    testSuite.Setup(t)
    defer testSuite.Teardown(t)
    
    testSuite.RunInTestTransaction(t, func(tx *gorm.DB) {
        // Database operations here will be rolled back
        user := &model.User{Email: "test@example.com"}
        tx.Create(user)
        
        // Assertions
        var count int64
        tx.Model(&model.User{}).Where("email = ?", "test@example.com").Count(&count)
        assert.Equal(t, int64(1), count)
    })
}

Debugging Tests

1. Verbose Output

go test -v ./tests/...

2. Run Specific Tests

go test -run TestSpecificFunction ./tests/unit/...

3. Test Coverage

go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage.html

4. Race Condition Detection

go test -race ./tests/...

Contributing

When adding new tests:

  1. Follow naming conventions: Use clear, descriptive names
  2. Add documentation: Include comments for complex test logic
  3. Update fixtures: Add new fixtures for new features
  4. Maintain coverage: Ensure new code has adequate test coverage
  5. Test edge cases: Include tests for error conditions and edge cases

Troubleshooting

Common Issues

  1. Database Connection Failed

    • Check PostgreSQL is running
    • Verify environment variables
    • Ensure test database exists
  2. Tests Failing Intermittently

    • Check for race conditions
    • Ensure proper test isolation
    • Review shared state between tests
  3. Slow Test Execution

    • Profile test performance
    • Optimize database operations
    • Consider using test transactions
  4. Import Errors

    • Run go mod tidy
    • Check module dependencies
    • Verify Go version compatibility

For additional help, refer to the main project documentation or contact the development team.