Skip to content
This repository was archived by the owner on Oct 24, 2025. It is now read-only.

How to write a test

Zoka-tech edited this page Apr 2, 2025 · 12 revisions

Test Guidelines

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.

General Structure

  • Follow the pattern: t.Run("should ...", func(t *testing.T) { ... })
  • Structure tests into three parts:
    1. Setup / Given
    2. Execution / When
    3. Assertions / Then

Test Naming

  • 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", ...)

Mock Usage

  • 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.

When to Use What

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

Good Practices

  • 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.

Examples

Once you know what to test and have the environment ready, this section shows you an example for every layer.

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

Service Layer

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

Server Layer

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")
})
Clone this wiki locally