PocketBase exposes several test mocks and stubs (eg. tests.TestApp
,
tests.ApiScenario
, tests.MockMultipartData
, etc.) to help you write unit and
integration tests for your app.
You could find more information in the
github.com/pocketbase/pocketbase/tests
sub package, but here is a simple example.
Setup
Let's say that we have a custom API route GET /my/hello
that requires admin authentication:
// main.go
package main
import (
"log"
"net/http"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func bindAppHooks(app core.App) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/my/hello",
Handler: func(c echo.Context) error {
return c.String(200, "Hello world!")
},
Middlewares: []echo.MiddlewareFunc{
apis.RequireAdminAuth(),
},
})
return nil
})
}
func main() {
app := pocketbase.New()
bindAppHooks(app)
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
Prepare test data
Now we have to prepare our test/mock data. There are several ways you can approach this, but the easiest
one would be to start your application with a custom test_pb_data
directory, eg.:
./pocketbase serve --dir="./test_pb_data" --automigrate=0
Go to your browser and create the test data via the Admin UI (both collections and records). Once
completed, terminate the process and commit test_pb_data
to your repo.
Integration test
To test the example endpoint, we want to:
- ensure it handles only GET requests
- ensure that it can be accessed only by admins
- check if the response body is properly set
Below is a simple integration test for the above test cases. We'll also use the test data created in the previous step.
// main_test.go
package main
import (
"net/http"
"testing"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tokens"
)
const testDataDir = "./test_pb_data"
func TestHelloEndpoint(t *testing.T) {
recordToken, err := generateRecordToken("users", "test@example.com")
if err != nil {
t.Fatal(err)
}
adminToken, err := generateAdminToken("test@example.com")
if err != nil {
t.Fatal(err)
}
// setup the test ApiScenario app instance
setupTestApp := func(t *testing.T) *tests.TestApp {
testApp, err := tests.NewTestApp(testDataDir)
if err != nil {
t.Fatal(err)
}
// no need to cleanup since scenario.Test() will do that for us
// defer testApp.Cleanup()
bindAppHooks(testApp)
return testApp
}
scenarios := []tests.ApiScenario{
{
Name: "try with different http method, eg. POST",
Method: http.MethodPost,
Url: "/my/hello",
ExpectedStatus: 405,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as guest (aka. no Authorization header)",
Method: http.MethodGet,
Url: "/my/hello",
ExpectedStatus: 401,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as authenticated app user",
Method: http.MethodGet,
Url: "/my/hello",
RequestHeaders: map[string]string{
"Authorization": recordToken,
},
ExpectedStatus: 401,
ExpectedContent: []string{"\"data\":{}"},
TestAppFactory: setupTestApp,
},
{
Name: "try as authenticated admin",
Method: http.MethodGet,
Url: "/my/hello",
RequestHeaders: map[string]string{
"Authorization": adminToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"Hello world!"},
TestAppFactory: setupTestApp,
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func generateAdminToken(email string) (string, error) {
app, err := tests.NewTestApp(testDataDir)
if err != nil {
return "", err
}
defer app.Cleanup()
admin, err := app.Dao().FindAdminByEmail(email)
if err != nil {
return "", err
}
return tokens.NewAdminAuthToken(app, admin)
}
func generateRecordToken(collectionNameOrId string, email string) (string, error) {
app, err := tests.NewTestApp(testDataDir)
if err != nil {
return "", err
}
defer app.Cleanup()
record, err := app.Dao().FindAuthRecordByEmail(collectionNameOrId, email)
if err != nil {
return "", err
}
return tokens.NewRecordAuthToken(app, record)
}