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/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) func bindAppHooks(app core.App) { app.OnServe().BindFunc(func(se *core.ServeEvent) error { se.Router.Get("/my/hello", func(e *core.RequestEvent) error { return e.JSON(http.StatusOK, "Hello world!") }).Bind(apis.RequireSuperuserAuth()) return se.Next() }) } 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, e.g.:

    ./pocketbase serve --dir="./test_pb_data" --automigrate=0

    Go to your browser and create the test data via the Dashboard (both collections and records). Once completed you can stop the server (you could also 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 superusers
    • 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/core" "github.com/pocketbase/pocketbase/tests" ) const testDataDir = "./test_pb_data" func generateToken(collectionNameOrId string, email string) (string, error) { app, err := tests.NewTestApp(testDataDir) if err != nil { return "", err } defer app.Cleanup() record, err := app.FindAuthRecordByEmail(collectionNameOrId, email) if err != nil { return "", err } return record.NewAuthToken() } func TestHelloEndpoint(t *testing.T) { recordToken, err := generateToken("users", "test@example.com") if err != nil { t.Fatal(err) } superuserToken, err := generateToken(core.CollectionNameSuperusers, "test@example.com") if err != nil { t.Fatal(err) } // setup the test ApiScenario app instance setupTestApp := func(t testing.TB) *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, e.g. 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", Headers: map[string]string{ "Authorization": recordToken, }, ExpectedStatus: 401, ExpectedContent: []string{"\"data\":{}"}, TestAppFactory: setupTestApp, }, { Name: "try as authenticated admin", Method: http.MethodGet, URL: "/my/hello", Headers: map[string]string{ "Authorization": superuserToken, }, ExpectedStatus: 200, ExpectedContent: []string{"Hello world!"}, TestAppFactory: setupTestApp, }, } for _, scenario := range scenarios { scenario.Test(t) } }