Integration Test in Golang

Abdulrhman Almahaini
Insider Engineering
5 min readJul 11, 2022

--

integration test in the testing pyramid

After struggling with integrated testing, and writing it using Golang, I discovered that the resources in that field are few, and if there are any they are not enough and do not elaborate the idea sufficiently. Because of that, I decided to write about this subject hoping that you will find your answers here easily and directly.

What is Integration Testing:

Applications consist of multiple modules, the interaction between those modules will give the users the output and result they want. In testing terminology when a programmer wants to test this interaction and check if the data flow is working as expected and giving the expected output, this is called an Integration test. This kind of test is so important because programmers should write their tests without mocking the various dependencies, unlike Unit tests where programmers may mock the different dependencies and add some expectations to those dependencies.

Since the Integration test contains no mocking, the data flow will behave exactly as it would if it had been called by an end-user.

Integration Test vs Unit Test:

Actually, both of them complete each other, however, Unit tests are more about testing the behavior of a single unit (function, method, or certain piece of code). In this type of testing, programmers will use fake data and mocking objects to define and expect the behavior of the unit dependencies. The result of it will be asserting the expected result of this unit. Unit test is more straightforward and easy to implement because it is not caring about what is going on in the dependent methods. It will instead expect those dependent methods or services and continue with asserting the output of the tested unit.

Writing Unit tests will make the process of writing code faster and more robust.

On the other hand, as I said before, the Integration test will cover the interaction of those dependencies so there will be no mocking and less fake data, and as I realized and practiced only the third party can be mocked. For example, if we are calling a third-party API in that case we can mock this third party and expect its output. Furthermore, since the Integration test is made to test the whole workflow we have to assert database insertion and other transactions. Also, we have to assert other storage behavior (S3 and Redis) if they are used. All of that will make the Integration test more complicated and might fail easily if any module changes, so it has to be maintained more often than the Unit test.

The Integration test is like the container for the Unit test where all the small units will be tested indirectly inside it.

Integration test in GoLang:

In this section, I will give a simple example of how we wrote an Integration test in GoLang and try to explain each part to make it as simple as possible.

As I said above, in the Integration test we are testing the whole workflow and checking if a certain endpoint is giving the expected response or not. Depending on that we can change our code if it is not giving the expected result.

A simple example for every developer is an endpoint that receives data and stores it in the database. Another endpoint that gets a request and should return all data from the database.

Let’s take a look at the main.go file below as an example.

In this file as you can notice we have two main endpoints, one for storing some data in DB and the other one for getting all the data from DB.

We are using the gin/gonic library to manage the context and requests, in the main function we are making the dependencies ready for example DB driver, and creating an instance of that driver so we can pass it and use it in our code. This main function will call our setupServer function that bundles those two endpoints.

In order to make a test for those endpoints we will create a file main_test.go in the same directory as our main.go file.

The testing file consists of some important parts:

  • Set server where we are identifying the dependencies we will use in the test and we call the same setupServer function that exists in the main.go that will identify the tested endpoints.
  • Test functions that get an instance of *testing.T as input and inside those functions we will define our test cases.

The test cases will have the structure below:

The name of the test case should explain what we are testing in some detail.

In each test case as you can notice we are calling a newIntegration function. This function will initialize our test by creating a fake container for MySql (or Redis if needed) cause we don’t want to make changes to the real container while our test is running and will return an instance we can use in our test. Also, it will return a teardown call-back that we want it to run after our test case finished running (for now this teardown call-back will only truncate the DB we made changes to while running a test).

In addition, a test case includes the request we will send or the endpoint we want to test, along with header variables, if any, and the body we should use for that endpoint.

The last part of any test case is the assertion part where we are asserting the response we received after sending the request to the tested endpoint and those assertions will define if we got the expected result or not.

A very important point should be brought up since we are making an Integration test we may need to insert some data to DB (or create some files in S3) in order to make our flow work correctly. For example, you can check this test case.

Here as you can notice we are inserting some data using the fake DB instance we got from the newIntegration method before sending the request to the tested endpoint, and since this test case checks if we will get the expected data, we will assert that we got a response that has this inserted raws.

Note that if we did not do the insertion before sending the request, our test case will fail. Because our endpoint will return no data and in that case, our assertion will fail.

Conclusion:

In this article, I tried to explain how much Integration testing is important in the development cycle because by using it we will be able to check the workflow of a certain endpoint and make assertions and expectations of its responses. Also, I gave a simple example in GoLang to show how we can write an Integration test using that language.

You can find the example project by clicking on this link example project

I hope that you enjoyed reading this article and find it helpful. See you again in another article.

--

--

Abdulrhman Almahaini
Insider Engineering

software developer in Insider, with 7+ experience in backend development.