Our Blog.

Expert thoughts from our software developers and design leaders.

  • Manuel Perez on October 10, 2019

    How to mock an external service for tests in GO

    From my experience as a software developer, an issue that I have dealt when building an application are the dependencies for external services. A test with external dependencies where you don’t have much control can fail if there is a change in the service and the outcome is not the expected, with this in mind, we need to ensure the non-dependence of external services when running our tests. The most effective way to do this is mocking those dependencies.

    In this post, I will focus on the functional tests of an application. I will also talk about how to mock an external service to not rely on it when running our functional tests.

    To explain how to mock an external service, I’ll walk you step by step through an example.

    Let’s say that we have an endpoint, that is using a third-party package called holidays that is making HTTP requests to an external service to get such holidays.

    # /actions/holidays.go
    package actions
    
    import (
        "time"
        "github.com/somebody/holidays"
        "github.com/somebody/holidays/filters"
    )
    
    func Holidays(w http.ResponseWriter, r *http.Request) {
        currentMonth := int(time.Now().Month())
    
        filters := filters.Params{
            Months: []int{currentMonth}
        }
        
        message := "There are not holidays"
        
        days, _ := holidays.GetHolidays(filters)
        if len(days) > 0 {
            message = fmt.Sprintf("Holidays for this month: %v", days)
        }
    
        fmt.Fprintf(w, message)
    }
    

    Step 1: Separating the external service logic from our business logic.

    The environment where we executed the tests should not depend on any external service. Since we’re only testing the functionalities of our application and how it behaves with any input, regarding external services, we only care about the outcome of it.

    To achieve a higher level of control over our tests, we need to move all the holidays-service-logic to a separate package.

    # /lib/holidays/holidays.go
    package holidays
    
    import (
        "github.com/somebody/holidays"
        "github.com/somebody/holidays/filters"
    )
    
    func Holidays(months []int) ([]int, error) {
        filters := filters.Params{
            Months: months
        }
        days, err := holidays.GetHolidays(filters)
    
        return days, err
    }
    

    Once the logic is separated we have to make sure we must call the external service using the new package from our actions package.

    # /actions/holidays.go
    import (
        "project/lib/holidays"
    )
    
    func Holidays(w http.ResponseWriter, r *http.Request) {
        ...
        days, _ := holidays.Holidays(months)
        ...
    }
    

    Step 2: Abstracting the use of the service through an interface.

    So far, this solution doesn’t solve the problem, the tests are still executing all the holidays-service-logic so we are still making HTTP requests that we don’t want to be made. We have to do something else.

    To mock the external service, we can abstract all its logic within an interface which we can implement elsewhere and use it by our functional tests, returning the outcome we need to test the behavior of our application.

    Here is how such an interface would look like:

    # /lib/holidays/holidays.go
    package holidays
    
    import (
        "github.com/somebody/holidays"
        "github.com/somebody/holidays/filters"
    )
    
    var Client ServiceFunctions = holidaysService{}
    
    type ServiceFunctions interface {
        Holidays([]int) ([]int, error)
    }
    
    type holidaysService struct{}
    
    func (hs holidaysService) Holidays(months []int) ([]int, error) {
        filters := filters.Params{
            Months: months
        }
        days, err := holidays.GetHolidays(filters)
        return days, err
    }
    

    Client is a global variable that receives as value any type that implements the ServiceFunctions interface, by default it uses holidaysService{} who communicate with the external service but on the side of the tests, we can update its value with another type that implements the same interface by returning the outcome we want.

    var Client ServiceFunctions = holidaysService{}
    

    We have to make a change in the way of calling the external service, now it would be through our Client global variable.

    # /actions/holidays.go
    
    func Holidays(w http.ResponseWriter, r *http.Request) {
        ...
        days, _ := holidays.Client.Holidays(months)
        ...
    }
    

    Step 3: Create the mock of our interface.

    Once all the functions of the holidays-service are abstracted, we need to create another type that implements the same interface with the intent to use it as a dummy object for our tests. In this case, our new Holidays function we’ll return a list of hard-coded days and an error.

    # /lib/holidays/holidays_mock.go
    package holidays
    
    var (
        Days []int{}
        HolidaysError error
    )
    
    type ServiceFuncMock struct{}
    
    func (sfm ServiceFuncMock) Holidays(months []int) ([]int, error) {
        return Days, HolidaysError
    }
    

    We can make this interface as dynamic as we want, we can return different values in order to test how our application behaves.

    Step 4: How to use the mock?

    Given our test is still depending on external service. We will replace the holidays-service call by the simulation we created.

    We assign an instance from our mock to the Client variable we defined before:

    holidays.Client = holidays.ServiceFuncMock{}
    

    Here we are setting the value for our Client, we’re using the ServiceFuncMock "test interface" as the default interface.

    Here is an example of how to test our application with no external dependencies.

    Test 1: In this test, we simulate the response from the holiday-service with an empty list of holidays and our application should display “There are not holidays”.

    # /actions/holidays_test.go
    package actions
    
    import (
        ...
        "project/lib/holidays"
    )
    
    func Test_Holidays_Without_Days(t *testing.T) {
        holidays.Client = holidays.ServiceFuncMock{}
    
        req, _ := http.NewRequest("GET", "/holidays", nil)
        ...
    
        assert.Contains(t, res.Body.String(), "There are not holidays")
    }
    

    Test 2: In this case, we set a different response for the days that the Holidays function will return.

    # /actions/holidays_test.go
    
    ...
    
    func Test_Holidays_With_Days(t *testing.T) {
        holidays.Client = holidays.ServiceFuncMock{}
        holidays.Days = []int{1, 3, 5}
    
        req, _ := http.NewRequest("GET", "/holidays", nil)
        ...
    
        assert.Contains(t, res.Body.String(), "holidays for this month")
        assert.Contains(t, res.Body.String(), "1")
        assert.Contains(t, res.Body.String(), "3")
        assert.Contains(t, res.Body.String(), "5")
    }
    

    As you can see, mocking external services is an effective method when testing our application’s features that require the use of them. This method also serves us well to decrease the coupling of our application regarding external services.

    Thanks a lot for reading! Keep on learning and coding!

    Continue Reading
  • Manuel Perez on July 30, 2015

    A Happy Path To CSM

    Since I joined to the company and during the implementation of projects in which I participated, I have conducted a serie of activities for the management of those projects, activities and daily meetings, monthly, retrospectives among others, all this because in our company uses Scrum as a framework for managing our projects. Deepening about Scrum, it woke up in my desire to learn and perform the activities of a ScrumMaster.

    Sponsored by our company on March 2 of this year I traveled to Bogota along with two co-workers, Bryan and Jessica, for the ScrumMaster certification course, the course lasted two great days, and while is true that we get to the course with a lot of practical knowledge about the Scrum/Agile world, Kleer (the company who conducted the course) used every possible tool to establish these concepts and secure them in a dynamic and practice way.

    That was the most exciting part of the course, and where I was putting my expectations, because during flight to the CSM many times I wondered if it was possible, I mean, to prepare a group of people to perform activities of a ScrumMaster in two days. It was great to notice how with the passing of activities we were gradually increasing most knowledges about Scrum and the role of ScrumMaster and that was gaining sense why those meetings and activities that as company member we have done in each of our projects.

    After completing the course Bryan, Jessica and I have been sharing our fresh ideas and experience with our team through presentations, that in order to put in practice what we have learned and also we wanted to get feedback from our own knowledge, at the end we answered some doubts that any of them could probably have.

    • Why do we perform these activities?
    • Why there is a daily meeting where we talked about the worked of the last day?
    • Whose depend certain activities?
    • Who to turn when a problem occurs?

    Our goal as ScrumMaster when resolving these doubts beside finishing with them, it was to improve the self-management of members team that finally ends up guaranteeing better quality and value for our clients as well as the growth of ourselves as a professionals.

    Ultimately my participation in the CSM was a great experience, I had the opportunity to interact with people from different professions and countries and provide feedback of knowledge, I met places of the city of Bogota where the course was held during free time and achieve one of the course objectives that it was get the ScrumMaster certification.

    Continue Reading