@@ -72,151 +72,152 @@ func TestMyScenario(t *testing.T) {
7272### IMPORTANT: Database Backend Requirements
7373
7474All tests MUST be tested with multiple database backends to ensure compatibility:
75- - ** SQLite ** (in-memory, for fast tests)
75+
7676- ** PostgreSQL** (using testcontainers)
7777- ** Aerospike** (using testcontainers)
7878
79- ### Database Backend Test Pattern
79+ ### Unified Container Management (RECOMMENDED)
80+
81+ ** NEW: As of 2025, use the unified ` UTXOStoreType ` option in TestDaemon for automatic container management.**
82+
83+ This approach eliminates boilerplate container initialization code and ensures consistent setup across all tests.
84+
85+ #### Simple Pattern - Single Backend Test
8086
8187``` go
8288package smoke
8389
8490import (
85- " os"
8691 " testing"
87-
8892 " github.com/bsv-blockchain/teranode/daemon"
89- " github.com/bsv-blockchain/teranode/test/utils/aerospike"
90- " github.com/bsv-blockchain/teranode/test/utils/postgres"
9193 " github.com/stretchr/testify/require"
9294)
9395
94- func init () {
95- os.Setenv (" SETTINGS_CONTEXT" , " test" )
96- }
97-
98- // Test with SQLite (in-memory)
99- func TestMyFeatureSQLite (t *testing .T ) {
100- utxoStore := " sqlite:///test"
96+ func TestMyFeature (t *testing .T ) {
97+ SharedTestLock.Lock ()
98+ defer SharedTestLock.Unlock ()
10199
102- t.Run (" scenario1" , func (t *testing.T ) {
103- testScenario1 (t, utxoStore)
104- })
105- t.Run (" scenario2" , func (t *testing.T ) {
106- testScenario2 (t, utxoStore)
100+ // Container automatically initialized and cleaned up
101+ td := daemon.NewTestDaemon (t, daemon.TestOptions {
102+ EnableRPC: true ,
103+ EnableValidator: true ,
104+ SettingsContext: " dev.system.test" ,
105+ UTXOStoreType: " aerospike" , // "aerospike", "postgres"
107106 })
108- }
107+ defer td. Stop (t)
109108
110- // Test with PostgreSQL
111- func TestMyFeaturePostgres (t *testing .T ) {
112- // Setup PostgreSQL container
113- utxoStore , teardown , err := postgres.SetupTestPostgresContainer ()
109+ err := td.BlockchainClient .Run (td.Ctx , " test" )
114110 require.NoError (t, err)
115111
116- defer func () {
117- _ = teardown ()
118- }()
112+ // Test implementation...
113+ }
114+ ```
115+
116+ #### Multi-Backend Pattern - Testing All Backends
119117
118+ ``` go
119+ package smoke
120+
121+ import (
122+ " testing"
123+ " github.com/bsv-blockchain/teranode/daemon"
124+ " github.com/stretchr/testify/require"
125+ )
126+
127+ // Test with Aerospike (default, recommended for most tests)
128+ func TestMyFeatureAerospike (t *testing .T ) {
120129 t.Run (" scenario1" , func (t *testing.T ) {
121- testScenario1 (t, utxoStore )
130+ testScenario1 (t, " aerospike " )
122131 })
123132 t.Run (" scenario2" , func (t *testing.T ) {
124- testScenario2 (t, utxoStore )
133+ testScenario2 (t, " aerospike " )
125134 })
126135}
127136
128- // Test with Aerospike
129- func TestMyFeatureAerospike (t *testing .T ) {
130- // Setup Aerospike container
131- utxoStore , teardown , err := aerospike.InitAerospikeContainer ()
132- require.NoError (t, err)
133-
134- t.Cleanup (func () {
135- _ = teardown ()
136- })
137-
137+ // Test with PostgreSQL
138+ func TestMyFeaturePostgres (t *testing .T ) {
138139 t.Run (" scenario1" , func (t *testing.T ) {
139- testScenario1 (t, utxoStore )
140+ testScenario1 (t, " postgres " )
140141 })
141142 t.Run (" scenario2" , func (t *testing.T ) {
142- testScenario2 (t, utxoStore )
143+ testScenario2 (t, " postgres " )
143144 })
144145}
145146
146147// Shared test implementation
147- func testScenario1 (t *testing .T , utxoStore string ) {
148+ func testScenario1 (t *testing .T , storeType string ) {
148149 SharedTestLock.Lock ()
149150 defer SharedTestLock.Unlock ()
150151
151152 td := daemon.NewTestDaemon (t, daemon.TestOptions {
152153 EnableRPC: true ,
153154 EnableValidator: true ,
154155 SettingsContext: " dev.system.test" ,
155- SettingsOverrideFunc: func (s *settings.Settings ) {
156- url , err := url.Parse (utxoStore)
157- require.NoError (t, err)
158- s.UtxoStore .UtxoStore = url
159- },
156+ UTXOStoreType: storeType, // Automatic container management
160157 })
161158 defer td.Stop (t)
162159
160+ err := td.BlockchainClient .Run (td.Ctx , " test" )
161+ require.NoError (t, err)
162+
163163 // Test implementation...
164164}
165165```
166166
167- ### Database URL Formats
167+ #### Available Store Types
168168
169- - ** SQLite ** : ` "sqlite:///test "` (in-memory database )
170- - ** PostgreSQL ** : Connection string returned by ` SetupTestPostgresContainer() `
171- - ** Aerospike ** : URL returned by ` InitAerospikeContainer() ` (e.g., ` "aerospike://host:port/namespace?set=test&expiration=10m" ` )
169+ - ` "aerospike "` - Aerospike container (production-like, recommended )
170+ - ` "postgres" ` - PostgreSQL container (production-like)
171+ - ` "" ` (empty) - No automatic container (uses default settings )
172172
173- ### Helper Functions for Database Setup
173+ #### Benefits of Unified Approach
174174
175- #### PostgreSQL Container Setup
175+ ✅ ** No boilerplate** - No manual container initialization
176+ ✅ ** Automatic cleanup** - Containers cleaned up with ` td.Stop(t) `
177+ ✅ ** Consistent setup** - Same initialization across all tests
178+ ✅ ** Type-safe** - Compile-time validation of store types
179+ ✅ ** Less code** - ~ 10 lines reduced per test
176180
177- ``` go
178- import " github.com/bsv-blockchain/teranode/test/utils/postgres"
181+ ### Legacy Pattern (Manual Container Setup)
179182
180- utxoStore , teardown , err := postgres.SetupTestPostgresContainer ()
181- require.NoError (t, err)
182- defer func () {
183- _ = teardown ()
184- }()
185- ```
183+ ** NOTE: This pattern is deprecated. Use ` UTXOStoreType ` option instead.**
184+
185+ If you encounter existing tests using manual container setup, they should be refactored to use the unified approach above.
186186
187- #### Aerospike Container Setup
187+ <details >
188+ <summary >Click to see legacy pattern (for reference only)</summary >
188189
189190``` go
190- import " github.com/bsv-blockchain/teranode/test/utils/aerospike"
191+ // OLD PATTERN - Do not use for new tests
192+ func TestMyFeatureAerospike (t *testing .T ) {
193+ // Manual container setup (deprecated)
194+ utxoStore , teardown , err := aerospike.InitAerospikeContainer ()
195+ require.NoError (t, err)
196+ t.Cleanup (func () {
197+ _ = teardown ()
198+ })
191199
192- utxoStore , teardown , err := aerospike.InitAerospikeContainer ()
193- require.NoError (t, err)
194- t.Cleanup (func () {
195- _ = teardown ()
196- })
200+ td := daemon.NewTestDaemon (t, daemon.TestOptions {
201+ SettingsContext: " dev.system.test" ,
202+ SettingsOverrideFunc: func (s *settings.Settings ) {
203+ url , err := url.Parse (utxoStore)
204+ require.NoError (t, err)
205+ s.UtxoStore .UtxoStore = url
206+ },
207+ })
208+ defer td.Stop (t)
209+ // ...
210+ }
197211```
198212
199- ### Configuring TestDaemon with Custom UTXO Store
200-
201- ``` go
202- td := daemon.NewTestDaemon (t, daemon.TestOptions {
203- SettingsContext : " dev.system.test" ,
204- SettingsOverrideFunc : func (s *settings.Settings ) {
205- // Parse the UTXO store URL
206- url , err := url.Parse (utxoStore)
207- require.NoError (t, err)
208- // Override the UTXO store setting
209- s.UtxoStore .UtxoStore = url
210- },
211- })
212- ```
213+ </details >
213214
214215### Best Practices for Multi-Database Testing
215216
216- 1 . ** Always test all three backends ** - SQLite for speed, PostgreSQL and Aerospike for production parity
217- 2 . ** Use shared test functions ** - Write test logic once, parameterize with ` utxoStore `
218- 3 . ** Handle cleanup properly ** - Always defer teardown functions for containers
219- 4 . ** Set SETTINGS_CONTEXT ** - Add ` os.Setenv("SETTINGS_CONTEXT", "test") ` in init()
217+ 1 . ** Use UTXOStoreType option ** - Always prefer the unified container management approach
218+ 2 . ** Test with two store types ** - Aerospike and PostgreSQL
219+ 3 . ** Use shared test functions ** - Write test logic once, parameterize with ` storeType `
220+ 4 . ** Aerospike is default ** - Most tests should use Aerospike as it's closest to production
2202215 . ** Use subtests** - Organize scenarios with ` t.Run() ` for better test output
2212226 . ** Consider test isolation** - Each database backend should be independent
222223
0 commit comments