-
Couldn't load subscription status.
- Fork 2
How to write a test
This section provides general rules and best practices for writing tests in this project. These guidelines help ensure tests are consistent, readable, and easy to maintain.
- Follow the pattern:
t.Run("should ...", func(t *testing.T) { ... }) - Structure tests into three parts:
- Setup / Given
- Execution / When
- Assertions / Then
- Test function names should clearly describe what’s being tested.
func TestUserService_CreateUser(t *testing.T) { ... }
- Inside the test, use descriptive
t.Run(...)names:- ✅
t.Run("should create user successfully", ...) - ✅
t.Run("should return error if email is invalid", ...)
- ✅
- Avoid vague names like:
- ❌
t.Run("test1", ...) - ❌
t.Run("check something", ...)
- ❌
- Use mocks to isolate logic in Service and Server layer tests.
- All mocks are placed in
/internal/storage/_mock/or/internal/service/_mock/ - Mocks are generated using
make generate - Run mock generation whenever a storage interface is created or updated.
| Layer | Type | Uses Mocks? | Needs DB? | Notes |
|---|---|---|---|---|
| Storage | Integration test | ❌ | ✅ | Real DB, real queries |
| Service | Unit test | ✅ | ❌ | Tests logic using mocks |
| Server | HTTP test | ✅/❌ | ❌/✅ | Often uses Fiber test tools |
| Middleware | Unit test | ❌ | ❌ | Can be tested with HTTP tools |
- Use
assert.NoError(t, err)to check for errors before other assertions. - Prefer specific assertions over general ones:
- ✅
assert.Equal(t, 3, count) - ❌
assert.True(t, count == 3)
- ✅
- Make tests fast and independent – no test should depend on another.
Once you know what to test and have the environment ready, this section shows you an example for every layer.
The test below ensures that a TreeCluster entity is successfully created with the name "test". It verifies that no errors occur during creation, the resulting object is not nil, and its fields are correctly initialized. Key assertions include checking the name, generated ID, timestamps (CreatedAt, UpdatedAt), and default values for other fields such as address, description, moisture level, coordinates, watering status, soil condition, and archive status.
t.Run("should create tree cluster with name", func(t *testing.T) {
// given
suite.ResetDB(t)
suite.InsertSeed(t, "internal/storage/postgres/seed/test/treecluster")
r := NewTreeClusterRepository(suite.Store, mappers)
createFn := func(tc *entities.TreeCluster, _ storage.TreeClusterRepository) (bool, error) {
tc.Name = "test"
return true, nil
}
// when
got, err := r.Create(context.Background(), createFn)
// then
assert.NoError(t, err)
assert.NotNil(t, got)
assert.Equal(t, "test", got.Name)
assert.NotZero(t, got.ID)
assert.WithinDuration(t, got.CreatedAt, time.Now(), time.Second)
assert.WithinDuration(t, got.UpdatedAt, time.Now(), time.Second)
assert.Nil(t, got.Region)
assert.Empty(t, got.Trees)
assert.Equal(t, "", got.Address)
assert.Equal(t, "", got.Description)
assert.Equal(t, 0.0, got.MoistureLevel)
assert.Nil(t, got.Latitude)
assert.Nil(t, got.Longitude)
assert.Equal(t, entities.WateringStatusUnknown, got.WateringStatus)
assert.Equal(t, entities.TreeSoilConditionUnknown, got.SoilCondition)
assert.False(t, got.Archived)
assert.Nil(t, got.LastWatered)
})This test checks what happens when there's a problem getting the number of tree clusters. It sets up fake (mock) data sources and makes the cluster repository return an error on purpose. Then it runs the GetEvaluation function and makes sure:
- An error is returned.
- All numbers in the result (like sensor count, tree count, etc.) are zero.
- Lists like vehicles and regions are empty.
This makes sure the system handles errors properly and doesn’t return random or incorrect data.
t.Run("should return error when getting cluster count fails", func(t *testing.T) {
wateringPlanRepo := storageMock.NewMockWateringPlanRepository(t)
clusterRepo := storageMock.NewMockTreeClusterRepository(t)
treeRepo := storageMock.NewMockTreeRepository(t)
sensorRepo := storageMock.NewMockSensorRepository(t)
vehicleRepo := storageMock.NewMockVehicleRepository(t)
svc := evaluation.NewEvaluationService(clusterRepo, treeRepo, sensorRepo, wateringPlanRepo, vehicleRepo)
clusterRepo.EXPECT().GetCount(context.Background(), entities.TreeClusterQuery{}).Return(int64(0), errors.New("internal error"))
evaluation, err := svc.GetEvaluation(context.Background())
assert.Error(t, err)
assert.Equal(t, int64(0), evaluation.SensorCount)
assert.Equal(t, int64(0), evaluation.TreeClusterCount)
assert.Equal(t, int64(0), evaluation.TreeCount)
assert.Equal(t, int64(0), evaluation.WateringPlanCount)
assert.Equal(t, int64(0), evaluation.TotalWaterConsumption)
assert.Equal(t, int64(0), evaluation.UserWateringPlanCount)
assert.Empty(t, evaluation.VehicleEvaluation)
assert.Empty(t, evaluation.RegionEvaluation)
})This test checks that the health check endpoint works correctly. It sets up a simple app with a health check route, sends a GET request to /health, and makes sure:
- There’s no error in the request.
- The response status code is 200 OK.
This confirms the app is running and healthy.
t.Run("should return status 200 for health check", func(t *testing.T) {
app := fiber.New()
app.Use(middleware.HealthCheck(nil))
req := httptest.NewRequest("GET", "/health", nil)
resp, err := app.Test(req, -1)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
if resp != nil {
defer resp.Body.Close()
}
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, "Health check should return status 200")
})