This document describes the testing strategy for handling Community Edition (CE) and Enterprise Edition (ENT) test suites.
The codebase uses a factory pattern with init() functions to register enterprise implementations. This creates import cycle issues when tests try to import enterprise packages to trigger factory registration.
The preferred approach is to use a TestMain function in an enterprise-tagged file to import all enterprise features before tests run. This registers the factories via their init() functions.
Enterprise TestMain File (testmain_enterprise_test.go):
//go:build enterprise
// +build enterprise
package mypackage
import (
"os"
"testing"
// Import enterprise features to register factories before tests run
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/budget"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/edge_management"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/group_access"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/licensing"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/marketplace_management"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/plugin_security"
_ "github.com/TykTechnologies/midsommar/v2/enterprise/features/sso"
)
func TestMain(m *testing.M) {
// Enterprise factories are now registered via init()
os.Exit(m.Run())
}Key Points:
- The file MUST have
//go:build enterprisetag so it only compiles in enterprise builds - Use blank imports (
_) to trigger init() functions without using the packages - Import ALL enterprise features that the package's tests might need
- The TestMain function runs BEFORE any tests, ensuring factories are registered
- In CE builds, this file doesn't compile, and tests use CE factory stubs
Note on Import Cycles: Some packages (like services/) cannot import budget due to circular dependencies. Exclude those imports and add a comment explaining why.
Use this approach when:
- ✅ Multiple tests in the package create services
- ✅ You want to avoid duplicating test code
- ✅ Tests work in CE with limited functionality (using stub implementations)
- ✅ No import cycles exist with enterprise features
See these files for working examples:
auth/testmain_enterprise_test.go- Auth packageservices/testmain_enterprise_test.go- Services package (excludes budget)api/testmain_enterprise_test.go- API packageproxy/testmain_enterprise_test.go- Proxy package
Use separate test files with build tags for CE and ENT versions of tests that depend on services. This approach is useful when TestMain isn't sufficient or when you want completely different test implementations.
For any test that creates services (which need factory registration):
1. Enterprise Test File (*_enterprise_test.go):
//go:build enterprise
// +build enterprise
package mypackage
// Full test implementation with real service creation
func TestFeatureEnterprise(t *testing.T) {
db := setupTestDB(t)
service := services.NewService(db) // Works because ENT factories are registered
// ... full test logic ...
}2. Community Test File (*_community_test.go):
//go:build !enterprise
// +build !enterprise
package mypackage
// Placeholder test for CE build
func TestFeatureCommunity(t *testing.T) {
assert.True(t, true, "CE test framework operational")
t.Log("Full tests run in Enterprise Edition")
}api/analytics_handlers_enterprise_test.go- Full analytics testsapi/analytics_handlers_community_test.go- CE placeholder
services/chat_service_enterprise_test.go- Full chat testsservices/chat_service_community_test.go- CE placeholder
- ✅ Multiple tests in package create
services.NewService(db) - ✅ Tests should work in both CE and EE (with different behavior)
- ✅ No enterprise-specific test logic needed
- ✅ Package has no import cycles with enterprise features
- ✅ Tests need completely different implementations for CE vs EE
- ✅ Enterprise tests require features not available in CE
- ✅ TestMain approach creates import cycles
- ✅ Only a few tests need service creation
- ❌ Test only uses models directly (no service layer)
- ❌ Test uses HTTP handlers without service creation
- ❌ Test is pure unit test with no database
- ❌ Test doesn't call
services.NewService()or related factory methods
go test ./... # Runs all CE tests
go test ./api/... # Runs CE API testsgo test -tags enterprise ./... # Runs all ENT tests
go test -tags enterprise ./api/... # Runs ENT API tests- No Import Cycles: Enterprise packages don't need to be imported in CE builds
- Clean Separation: CE and ENT test logic is clearly separated
- Factory Registration: ENT tests automatically get factory registration through build system
- Compilation Success: Both CE and ENT builds compile and run successfully
- Backup Old Tests: Move original test files to
.bakbefore creating split versions - Consistent Naming: Use
*_enterprise_test.goand*_community_test.gosuffixes - Test Coverage: CE tests should have minimal placeholders, full tests in ENT
- Model Fields: Check model structures when creating test data (field names change)
When splitting an existing test file:
- Backup original:
mv test.go test.go.bak - Create
*_enterprise_test.gowith//go:build enterprisetag - Create
*_community_test.gowith//go:build !enterprisetag - Copy full test logic to enterprise file
- Add placeholder to community file
- Test CE build:
go test ./package/... - Test ENT build:
go test -tags enterprise ./package/... - Remove backup if successful
Consider these alternatives if build-tagged files become unwieldy:
- Mock Factories: Create test-only factories that don't require imports
- Test Helpers: Centralize service creation in test utilities
- Refactor Factories: Move factory registration to explicit calls instead of
init()