tests/

directory
v0.0.0-...-702f6d9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2024 License: AGPL-3.0

README

Using the Integration Testing Helpers

The integration testing helpers are a set of functions that reduce the boilerplate code required to write integration tests. They are located in the backend/tests/api/helpers.go.

Modeling a Request with TestRequest

You can model a request with the TestRequest struct:

type TestRequest struct {
 Method             string
 Path               string
 Body               *map[string]interface{}
 Headers            *map[string]string
 Role               *models.UserRole
 TestUserIDReplaces *string
}

Since Body and Headers are pointers, if they don't set them when creating a TestRequest, they will be nil.

Here is an example of creating a TestRequest, notice how instead of saying Headers: nil, we can simply omit the Headers field.

TestRequest{
  Method: fiber.MethodPost,
  Path:   "/api/v1/tags/",
  Body: &map[string]interface{}{
   "name":        tagName,
   "category_id": uuid.New(),
  },
}

This handles a lot of the logic for you, for example, if the body is not nil, it will be marshalled into JSON and the Content-Type header will be set to application/json.

Role represents the desired role for the request to be sent with, see models.UserRole for the available roles. This allows us to test our permission based authentication. Since this is a pointer, if it is nil, the request will be sent without a role.

TestUserIDReplaces allows us to use the userID of the created user in our tests. For example, say the path is /api/v1/users/:userID/, we can use h.StringToPointer(":userID") to replace the path param with the dynamically generated userID. The : prefix indicates to attempt to replace in the path. If there isn't a match in the path, nothing will happen. Else, without a : prefix, it will attempt to replace the corresponding JSON key in the path. For example, if the userID is passed in the body, use h.StringToPointer("user_id") and it will attempt the value of the key user_id in the body. Again, if there isn't a match, nothing will happen.

Initializing a Test

Simply call h.InitTest(t) to initialize a test. This will create a new database for the test and return an ExistingAppAssert struct that can be reused throughout the sequence of requests. The h.InitTest(...) function also accepts optional TestAppConfigurators that allow you to configure the test app before the test starts. You can define additional configurators in the backend/tests/api/helpers/test.go file.

Existing App Asserts

Since the test suite creates a new database for each test, we can have a deterministic database state for each test. However, what if we have a multi step test that depends on the previous steps database state? That is where ExistingAppAssert comes in! This will allow us to keep using the database from a previous step in the test. They also allow us to configure & use (or not use) a user across a sequence of requests. Additionally, the signature of tests allow for a chaining of tests, so you can run multiple tests in a sequence.

appAssert.TestOnStatus(
  h.TestRequest{
   Method:             fiber.MethodPost,
   Path:               fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID),
   Role:               &models.Super,
   TestUserIDReplaces: h.StringToPointer(":userID"),
  },
  fiber.StatusCreated,
 ).TestOnStatusAndTester(
  h.TestRequest{
   Method:             fiber.MethodDelete,
   Path:               fmt.Sprintf("/api/v1/users/:userID/follower/%s", clubUUID),
   Role:               &models.Super,
   TestUserIDReplaces: h.StringToPointer(":userID"),
  },
  h.TesterWithStatus{
   Status: fiber.StatusNoContent,
   Tester: func(eaa h.ExistingAppAssert, resp *http.Response) {
    var user models.User

    err := eaa.App.Conn.Where("id = ?", eaa.App.TestUser.UUID).Preload("Follower").First(&user)

    eaa.Assert.NilError(err)

    eaa.Assert.Equal(1, len(user.Follower))

    var club models.Club

    err = eaa.App.Conn.Where("id = ?", clubUUID).Preload("Follower").First(&club)

    eaa.Assert.NilError(err)

    eaa.Assert.Equal(0, len(club.Follower))
   },
  },
 ).Close()

Testers

Often times there are common assertions you want to make about the database, for example, if the object in the response is the same as the object in the database. We can create a lambda function that takes in the TestApp, *assert.A, and *http.Response and makes the assertions we want. We can then pass this function to the TesterWithStatus struct.

func AssertSampleCategoryBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response) uuid.UUID {
 return AssertCategoryBodyRespDB(eaa, resp, SampleCategoryFactory())
}

func AssertCategoryBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID {
 var respCategory models.Category

 err := json.NewDecoder(resp.Body).Decode(&respCategory)

 eaa.Assert.NilError(err)

 var dbCategories []models.Category

 err = eaa.App.Conn.Find(&dbCategories).Error

 eaa.Assert.NilError(err)

 eaa.Assert.Equal(1, len(dbCategories))

 dbCategory := dbCategories[0]

 eaa.Assert.Equal(dbCategory.ID, respCategory.ID)
 eaa.Assert.Equal(dbCategory.Name, respCategory.Name)

 eaa.Assert.Equal((*body)["name"].(string), dbCategory.Name)

 return dbCategory.ID
}

Why Close?

This closes the connection to the database. This is important because if you don't close the connection, we will run out of available connections and the tests will fail. Always call .close() on the last test request of a test. If you want to test a sequence of requests, call .close() on the last request of the sequence.

Testing that a Request Returns a XXX Status Code

Say you want to test hitting the [APP_ADDRESS]/health endpoint with a GET request returns a 200 status code.

 h.InitTest(t).TestOnStatus(
  h.TestRequest{
   Method: fiber.MethodGet,
   Path:   "/health",
  },
  fiber.StatusOK,
 ).Close()

Testing that a Request Returns a XXX Status Code and Assert Something About the Database

Say you want to test that a creating a catgory with POST [APP_ADDRESS]/api/v1/categories/ returns a 201

existingAppAssert.TestOnStatusAndTester(
  h.TestRequest{
   Method: fiber.MethodPost,
   Path:   "/api/v1/categories/",
   Body:   SampleCategoryFactory(),
   Role:   &models.Super,
  },
  h.TesterWithStatus{
   Status: fiber.StatusCreated,
   Tester: func(eaa h.ExistingAppAssert, resp *http.Response) {
    sampleCategoryUUID = AssertSampleCategoryBodyRespDB(eaa, resp)
   },
  },
 )

Testing that a Request Returns the Correct Error (Status Code and Message), and Assert Something About the Database

Say you want to test a bad request to POST [APP_ADDRESS]/api/v1/categories/ endpoint returns a 400 status code, the message is failed to process the request, and that a category was not created. We can leverage our errors defined in the error package to do this!

 h.InitTest(t).TestOnErrorAndTester(
  h.TestRequest{
   Method: fiber.MethodPost,
   Path:   "/api/v1/categories/",
   Body: &map[string]interface{}{
    "name": 1231,
   },
   Role: &models.Super,
  },
  h.ErrorWithTester{
   Error:  errors.FailedToParseRequestBody,
   Tester: AssertNoCategories,
  },
 ).Close()

Directories

Path Synopsis
api

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL