Get Started Testing an API Built With Golang
Time to read: 5 minutes
Writing code that "works" is the easy part. The real issue is writing code that "lasts". Whether it’s a change in team membership or a change in requirements, your code should be able to grow with the changes, by virtue of a well-defined architecture, and also identify breaking changes/unhandled edge cases that may arise due to said changes.
This article - the second in a series; aims to show you how testing will help you with the latter requirement. It will introduce testing in Golang and focus on unit testing a REST API. You will build on the first article to write tests for existing features, as well as employ test-driven development in the implementation of a new feature.
Prerequisites
To follow this tutorial, you will need the following:
Get started
If you already have the code from the first part of this series, you can skip this section. However, if you’re just joining in, get started by cloning the repository and change into the cloned directory by running the commands below:
Next, install the project dependencies using the following command.
Next, create a new PostgreSQL database named diary_app. You can do so by running the following command
Where prompted, provide the password associated with DB_USER.
Alternatively, create the database using your preferred tool of choice.
Next, make a copy of .env.example named .env.local using the following command.
Set up the test environment
Testify is one of the most popular testing packages for Golang and will be used in this article. Install it using the following command.
Next, create a new database named diary_app_test.
With that done, create a .env.test.local file from the .env.local file using the command below.
Doing this will provide a separate, local test environment configuration, preventing unexpected behavior.
Next, create a new file named main_test.go within the controller folder. This file will hold the global functions required to test all the endpoints for the application. Now, add the following code to the newly created file.
TestMain()
is an inbuilt function in Golang that allows more control over running tests. There are methods defined within the testing.M struct to access and run tests. It is a perfect place to run specific code before and after tests. Here, it is used to run migrations for the database and delete all tables after the test.
The router()
is a method to return an instance of the Gin router. It will come in handy when testing other functions for each endpoint.
In the setup()
function, the test environment variables are loaded and the database connection is established. The migrations are also run to create the appropriate database tables.
To ensure that every test starts with a clean slate, the teardown()
function is used to delete all the tables in the database. This function will be called when all the tests have been run.
The makeRequest()
function will be used to make requests to the various endpoints. The returned ResponseRecorder
can then be used for further assertions. In the event that the endpoint to be called requires authentication, the bearerToken()
function is used to get a token for the test user.
Write tests for a happy path
The first set of tests will ensure that, provided the right data, the API will return the correct response. To begin, create a new file in the controller directory named authentication_test.go, and add the following code.
The TestRegister()
sends a POST request with a mock user detail to the /auth/register
endpoint and asserts that the appropriate status code was returned.
In addition to ensuring that the expected status was returned after logging in as a user, the TestLogin()
function also asserts that the response contains the jwt
.
Still, within the controller folder, create another file to test the endpoints for entries named entry_test.go. Add the following code to it
Both TestAddEntry()
and TestGetAllEntries()
ensure that a new entry can be created and a list of entries retrieved with the appropriate status codes respectively.
Since the tests files are housed within the controller folder, use the following command to run all tests for the controller package specifically
You will see output similar to this:
Testing for edge cases
So far, you’ve written tests to verify that, given the correct parameters, the application will provide the expected results. However, testing goes beyond that, it also involves making sure that your application is able to handle edge cases or likely cases of misuse. In such cases, you want to be sure that the integrity of your application is not compromised.
To demonstrate this, consider the event that an authentication request without a password field is made to your API. Your code already specifies a required binding which ensures that a password must be provided. However, you would like to ensure that if absent, an appropriate error response code and message should be returned.
Instead of diving straight into the code, this is a perfect opportunity to try your hands at a testing paradigm known as TDD (Test-driven Development) where you write the tests before writing the code that will make the tests pass.
Add the following function to authentication_test.go
Next, run your tests. This time you get a failure as shown below.
Because the returned error message did not match what you specified in the test, your code fails the test. This is the red phase of TDD where you write a test that fails. The next step is to write just enough code for the test to pass.
To do this, open the controller/authentication.go and update the Login()
function to match the following.
Remember to update your import statements.
After calling the ShouldBindJson()
function, if any errors are present, the tag and field of the first error are retrieved and used to generate a more friendly error message.
Run your tests again. This time everything passes.
Conclusion
In this article, you’ve looked at how to write tests for an existing codebase. It’s key to remember that testing is about making sure that your application is also prepared for the unexpected and as such your tests should cover as many “unpleasant” scenarios as you can think of.
You also briefly looked at TDD which, essentially, involves writing your tests before writing code. While it is still a heavily debated subject, one thing that cannot be overemphasized is that your code without tests is a disaster in the making. Your code should be backed by a suite of test cases regardless of whether or not they were written before the actual application code.
The entire codebase for this tutorial is available on GitHub. Feel free to explore further. Happy coding!
Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest to solve day-to-day problems encountered by users, he ventured into programming and has since directed his problem-solving skills at building software for both web and mobile.
A full-stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and content on several blogs on the internet. Being tech-savvy, his hobbies include trying out new programming languages and frameworks.
- Twitter: https://twitter.com/yemiwebby
- GitHub: https://github.com/yemiwebby
- Website: https://yemiwebby.com.ng/
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.