Skip to content

Commit 0b3286c

Browse files
authored
Merge pull request #1308 from mdelapenya/testcontainers-state
🔥 feat: add fiber.Services backed by testcontainers-go
2 parents d69b58f + 207acfd commit 0b3286c

File tree

11 files changed

+1330
-1
lines changed

11 files changed

+1330
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: "Test Testcontainers Services"
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
paths:
9+
- 'testcontainers/**'
10+
pull_request:
11+
paths:
12+
- 'testcontainers/**'
13+
14+
jobs:
15+
Tests:
16+
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
go-version:
20+
- 1.23.x
21+
- 1.24.x
22+
steps:
23+
- name: Fetch Repository
24+
uses: actions/checkout@v4
25+
26+
- name: Install Go
27+
uses: actions/setup-go@v5
28+
with:
29+
go-version: '${{ matrix.go-version }}'
30+
31+
- name: Run Test
32+
working-directory: ./testcontainers
33+
run: go test -v -race ./...

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ sidebar_position: 1
1111
[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord)
1212
![Linter](https://github.com/gofiber/contrib/workflows/Golangci%20Lint%20Check/badge.svg)
1313

14-
Repository for third party middlewares with dependencies.
14+
Repository for third party middlewares and service implementations, with dependencies.
1515

1616
</div>
1717

@@ -35,3 +35,7 @@ Repository for third party middlewares with dependencies.
3535
* [Socket.io](./socketio/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+socketio%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-socketio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
3636
* [Swagger](./swagger/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swagger%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swagger.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
3737
* [Websocket](./websocket/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+websocket%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
38+
39+
## 🥡 Service Implementations
40+
41+
* [Testcontainers](./testcontainers/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Testcontainers%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-testcontainers.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ use (
1818
./paseto
1919
./socketio
2020
./swagger
21+
./testcontainers
2122
./websocket
2223
)

testcontainers/README.md

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
---
2+
id: testcontainers
3+
---
4+
5+
# Testcontainers
6+
7+
![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=testcontainers*)
8+
[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord)
9+
![Test](https://github.com/gofiber/contrib/workflows/Test%20testcontainers/badge.svg)
10+
11+
A [Testcontainers](https://golang.testcontainers.org/) Service Implementation for Fiber.
12+
13+
:::note
14+
15+
Requires Go **1.23** and above
16+
17+
:::
18+
19+
## Common Use Cases
20+
21+
- Local development
22+
- Integration testing
23+
- Isolated service testing
24+
- End-to-end testing
25+
26+
## Install
27+
28+
:::caution
29+
30+
This Service Implementation only supports Fiber **v3**.
31+
32+
:::
33+
34+
```shell
35+
go get -u github.com/gofiber/fiber/v3
36+
go get -u github.com/gofiber/contrib/testcontainers
37+
```
38+
39+
## Signature
40+
41+
### NewModuleConfig
42+
43+
```go
44+
// NewModuleConfig creates a new container service config for a module.
45+
//
46+
// - The serviceKey is the key used to identify the service in the Fiber app's state.
47+
// - The img is the image name to use for the container.
48+
// - The run is the function to use to run the container. It's usually the Run function from the module, like [redis.Run] or [postgres.Run].
49+
// - The opts are the functional options to pass to the run function. This argument is optional.
50+
func NewModuleConfig[T testcontainers.Container](
51+
serviceKey string,
52+
img string,
53+
run func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error),
54+
opts ...testcontainers.ContainerCustomizer,
55+
) Config[T] {
56+
```
57+
58+
### NewContainerConfig
59+
60+
```go
61+
// NewContainerConfig creates a new container service config for a generic container type,
62+
// not created by a Testcontainers module. So this function best used in combination with
63+
// the [AddService] function to add a custom container to the Fiber app's state.
64+
//
65+
// - The serviceKey is the key used to identify the service in the Fiber app's state.
66+
// - The img is the image name to use for the container.
67+
// - The opts are the functional options to pass to the [testcontainers.Run] function. This argument is optional.
68+
//
69+
// This function uses the [testcontainers.Run] function as the run function.
70+
func NewContainerConfig[T *testcontainers.DockerContainer](serviceKey string, img string, opts ...testcontainers.ContainerCustomizer) Config[*testcontainers.DockerContainer]
71+
```
72+
73+
### AddService
74+
75+
```go
76+
// AddService adds a Testcontainers container as a [fiber.Service] for the Fiber app.
77+
// It returns a pointer to a [ContainerService[T]] object, which contains the key used to identify
78+
// the service in the Fiber app's state, and an error if the config is nil.
79+
// The container should be a function like redis.Run or postgres.Run that returns a container type
80+
// which embeds [testcontainers.Container].
81+
// - The cfg is the Fiber app's configuration, needed to add the service to the Fiber app's state.
82+
// - The containerConfig is the configuration for the container, where:
83+
// - The containerConfig.ServiceKey is the key used to identify the service in the Fiber app's state.
84+
// - The containerConfig.Run is the function to use to run the container. It's usually the Run function from the module, like redis.Run or postgres.Run.
85+
// - The containerConfig.Image is the image to use for the container.
86+
// - The containerConfig.Options are the functional options to pass to the [testcontainers.Run] function. This argument is optional.
87+
//
88+
// Use [NewModuleConfig] or [NewContainerConfig] helper functions to create valid containerConfig objects.
89+
func AddService[T testcontainers.Container](cfg *fiber.Config, containerConfig Config[T]) (*ContainerService[T], error) {
90+
```
91+
92+
## Types
93+
94+
### Config
95+
96+
The `Config` type is a generic type that is used to configure the container.
97+
98+
| Property | Type | Description | Default |
99+
|-------------|------|-------------|---------|
100+
| ServiceKey | string | The key used to identify the service in the Fiber app's state. | - |
101+
| Image | string | The image name to use for the container. | - |
102+
| Run | func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error) | The function to use to run the container. It's usually the Run function from the testcontainers-go module, like redis.Run or postgres.Run | - |
103+
| Options | []testcontainers.ContainerCustomizer | The functional options to pass to the [testcontainers.Run] function. This argument is optional. | - |
104+
105+
```go
106+
// Config contains the configuration for a container service.
107+
type Config[T testcontainers.Container] struct {
108+
// ServiceKey is the key used to identify the service in the Fiber app's state.
109+
ServiceKey string
110+
111+
// Image is the image name to use for the container.
112+
Image string
113+
114+
// Run is the function to use to run the container.
115+
// It's usually the Run function from the testcontainers-go module, like redis.Run or postgres.Run,
116+
// although it could be the generic [testcontainers.Run] function from the testcontainers-go package.
117+
Run func(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (T, error)
118+
119+
// Options are the functional options to pass to the [testcontainers.Run] function. This argument is optional.
120+
// You can find the available options in the [testcontainers website].
121+
//
122+
// [testcontainers website]: https://golang.testcontainers.org/features/creating_container/#customizing-the-container
123+
Options []testcontainers.ContainerCustomizer
124+
}
125+
```
126+
127+
### ContainerService
128+
129+
The `ContainerService` type is a generic type that embeds a [testcontainers.Container](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#Container) interface,
130+
and implements the [fiber.Service] interface, thanks to the Start, String, State and Terminate methods. It manages the lifecycle of a `testcontainers.Container` instance,
131+
and it can be retrieved from the Fiber app's state calling the `fiber.MustGetService` function with the key returned by the `ContainerService.Key` method.
132+
133+
The type parameter `T` must implement the [testcontainers.Container](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#Container) interface,
134+
as in the Testcontainers Go modules (e.g. [redis.RedisContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go/modules/redis#RedisContainer),
135+
[postgres.PostgresContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go/modules/postgres#PostgresContainer), etc.), or in the generic
136+
[testcontainers.DockerContainer](https://pkg.go.dev/github.com/testcontainers/testcontainers-go#GenericContainer) type, used for custom containers.
137+
138+
:::note
139+
140+
Since `ContainerService` implements the `fiber.Service` interface, container cleanup is handled automatically by the Fiber framework when the application shuts down. There's no need for manual cleanup code.
141+
142+
:::
143+
144+
```go
145+
type ContainerService[T testcontainers.Container] struct
146+
```
147+
148+
#### Signature
149+
150+
#####  Key
151+
152+
```go
153+
// Key returns the key used to identify the service in the Fiber app's state.
154+
// Consumers should use string constants for service keys to ensure consistency
155+
// when retrieving services from the Fiber app's state.
156+
func (c *ContainerService[T]) Key() string
157+
```
158+
159+
##### Container
160+
161+
```go
162+
// Container returns the Testcontainers container instance, giving full access to the T type methods.
163+
// It's useful to access the container's methods, like [testcontainers.Container.MappedPort]
164+
// or [testcontainers.Container.Inspect].
165+
func (c *ContainerService[T]) Container() T
166+
```
167+
168+
##### Start
169+
170+
```go
171+
// Start creates and starts the container, calling the [run] function with the [img] and [opts] arguments.
172+
// It implements the [fiber.Service] interface.
173+
func (c *ContainerService[T]) Start(ctx context.Context) error
174+
```
175+
176+
##### String
177+
178+
```go
179+
// String returns the service key, which uniquely identifies the container service.
180+
// It implements the [fiber.Service] interface.
181+
func (c *ContainerService[T]) String() string
182+
```
183+
184+
##### State
185+
186+
```go
187+
// State returns the status of the container.
188+
// It implements the [fiber.Service] interface.
189+
func (c *ContainerService[T]) State(ctx context.Context) (string, error)
190+
```
191+
192+
##### Terminate
193+
194+
```go
195+
// Terminate stops and removes the container. It implements the [fiber.Service] interface.
196+
func (c *ContainerService[T]) Terminate(ctx context.Context) error
197+
```
198+
199+
### Common Errors
200+
201+
| Error | Description | Resolution |
202+
|-------|-------------|------------|
203+
| ErrNilConfig | Returned when the config is nil | Ensure config is properly initialized |
204+
| ErrContainerNotRunning | Returned when the container is not running | Check container state before operations |
205+
| ErrEmptyServiceKey | Returned when the service key is empty | Provide a non-empty service key |
206+
| ErrImageEmpty | Returned when the image is empty | Provide a valid image name |
207+
| ErrRunNil | Returned when the run is nil | Provide a valid run function |
208+
209+
## Examples
210+
211+
You can find more examples in the [testable examples](./examples_test.go).
212+
213+
### Adding a module container using the Testcontainers Go's Redis module
214+
215+
```go
216+
package main
217+
218+
import (
219+
"fmt"
220+
"log"
221+
222+
"github.com/gofiber/fiber/v3"
223+
224+
"github.com/gofiber/contrib/testcontainers"
225+
tc "github.com/testcontainers/testcontainers-go"
226+
"github.com/testcontainers/testcontainers-go/modules/redis"
227+
)
228+
229+
func main() {
230+
cfg := &fiber.Config{}
231+
232+
// Define the base key for the module service.
233+
// The service returned by the [testcontainers.AddService] function,
234+
// using the [ContainerService.Key] method,
235+
// concatenates the base key with the "using testcontainers-go" suffix.
236+
const (
237+
redisKey = "redis-module"
238+
)
239+
240+
// Adding containers coming from the testcontainers-go modules,
241+
// in this case, a Redis and a Postgres container.
242+
243+
redisModuleConfig := testcontainers.NewModuleConfig(redisKey, "redis:latest", redis.Run)
244+
redisSrv, err := testcontainers.AddService(cfg, redisModuleConfig)
245+
if err != nil {
246+
log.Println("error adding redis module:", err)
247+
return
248+
}
249+
250+
// Create a new Fiber app, using the provided configuration.
251+
app := fiber.New(*cfg)
252+
253+
// Retrieve all services from the app's state.
254+
// This returns a slice of all the services registered in the app's state.
255+
srvs := app.State().Services()
256+
257+
// Retrieve the Redis container from the app's state using the key returned by the [ContainerService.Key] method.
258+
redisCtr := fiber.MustGetService[*testcontainers.ContainerService[*redis.RedisContainer]](app.State(), redisSrv.Key())
259+
260+
// Start the Fiber app.
261+
app.Listen(":3000")
262+
}
263+
```
264+
265+
### Adding a custom container using the Testcontainers Go package
266+
267+
```go
268+
package main
269+
270+
import (
271+
"fmt"
272+
"log"
273+
274+
"github.com/gofiber/fiber/v3"
275+
276+
"github.com/gofiber/contrib/testcontainers"
277+
tc "github.com/testcontainers/testcontainers-go"
278+
)
279+
280+
func main() {
281+
cfg := &fiber.Config{}
282+
283+
// Define the base key for the generic service.
284+
// The service returned by the [testcontainers.AddService] function,
285+
// using the [ContainerService.Key] method,
286+
// concatenates the base key with the "using testcontainers-go" suffix.
287+
const (
288+
nginxKey = "nginx-generic"
289+
)
290+
291+
// Adding a generic container, directly from the testcontainers-go package.
292+
containerConfig := testcontainers.NewContainerConfig(nginxKey, "nginx:latest", tc.WithExposedPorts("80/tcp"))
293+
294+
nginxSrv, err := testcontainers.AddService(cfg, containerConfig)
295+
if err != nil {
296+
log.Println("error adding nginx generic:", err)
297+
return
298+
}
299+
300+
app := fiber.New(*cfg)
301+
302+
nginxCtr := fiber.MustGetService[*testcontainers.ContainerService[*tc.DockerContainer]](app.State(), nginxSrv.Key())
303+
304+
// Start the Fiber app.
305+
app.Listen(":3000")
306+
}
307+
```

0 commit comments

Comments
 (0)