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.

    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) } }

    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.

    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) }