Golang Testing Using Ginkgo

Ginkgo with Golang

In this blog, I am going to discuss how to perform testing using Ginkgo, and write test cases for golang functions using the same. So, continue ahead for an introduction into the much used testing framework.

 

What is Ginkgo?

 

Ginkgo is a BDD(Behavior Driven Development)-style testing framework for Golang, and its preferred matcher library is Gomega.

 

Ginkgo will help you efficiently write descriptive and comprehensive tests.

 

For this purpose, first, we need to install Ginkgo. Next, we shall setup the global environment for the Test Suites. For this, there are a few functions, which are described in detail.

 

Getting Ginkgo

 

go get github.com/onsi/ginkgo/ginkgo  #the ginkgo CLI gets installed
go get github.com/onsi/gomega         #this will fetch the Gomega matcher library for you

 

ginkgo generate  #this command will create a sample test file. You can edit this file and add your tests into it.

go test  # this command is to run your tests. Alternatively, you can issue the following command:

ginkgo  #this will also do the same as the above command, which is to run your tests

 

Global setup Test Suites (before executing the test cases, we need to setup this environment)

 

Function BeforeSuite()

 

The BeforeSuite() blocks are executed once before any other specs are run. In a parallel environment, meaning when more there is more than one node running in parallel with another, then every parallel node will call the BeforeSuite().

 

Example: (I have used the sample purpose of establishing a database connection)

 


var _ = BeforeSuite(func() {

 // connect to database

 InitDb()

 err := Db.Ping()

 Expect(err).To(BeNil())

})

 

 

Function AfterSuite()

 

The AfterSuite() blocks are always run after all the specs have run. The success or failure of the specs is irrelevant to the AfterSuite(). It will run regardless of this outcome. Also, if the execution is  interrupted by a signal (something like ^C), the AfterSuite() will still be attempted to run by Ginkgo, before exiting or quitting.

 

Example: (here, i have written the AfterSuite() for cleaning the DB. The purpose is up to the individual developer)

 


var _ = AfterSuite(func() {

dbClient.Cleanup()

dbRunner.Stop()

})

 

 

Extracting common setup function BeforeEach()

 

The BeforeEach blocks are run before the ‘It’ blocks. The main use of the BeforeEach() is to remove duplication and share a common setup across tests. It should be noted that in a situation where there Describe and Context blocks nested, and multiple BeforeEach() are defined within these nested blocks, the outermost BeforeEach block is the one that is executed first.

 

Example:

 


 

var _ = Describe("Continent", func() {
   var continent String

   BeforeEach(func() {
       continent = “Asia”

   })

   It("matching big continent", func() {
       Expect(continent).To(Equal("Asia"))
    })
})

 

 

 

Function AfterEach()

 

Just as the BeforeEach() is run before the ‘It’ blocks, the AfterEach blocks are run after the ‘It’ blocks. Similarly, if there is a situation where there are nested Describe and Context blocks, and multiple AfterEach() functions are defined within them, then the  innermost AfterEach block is executed first, followed by the next innermost, and so on.

 

Example:

 


AfterEach(func() {
       Cleanup()
})

 

 

 

Function Describe()

 

We have mentioned this function in the above two descriptions. So what this Describe() does, is that it contains our specs. We will now see an example to test the loading of continents from JSON:


var _ = Describe("Continent", func() {
   var (
       bigContinent  Continent
       smallContinent Continent
   )

   BeforeEach(func() {
       bigContinent = Continent{
           Name:  "Asia",
           Population: "4.4 billion people",
           Countries :  48,
       }

       smallContinent = Continent{
           Name:  "Australia",
           Population: "24 million people",
           Countries:  4,
       }
   })

   Describe("Populous continent", func() {
       Context("Largest populous continent", func() {
           It("should have more than 50 million people", func() {
               Expect(bigContinent.ContinentNameByPopulation()).To(Equal(“Asia”))
           })
       })

       Context("Smallest populous continent", func() {
           It("should be a small", func() {
               Expect(smallContinent.ContinentNameByPopulation()).To(Equal("Australia"))
           })
       })
   })
})

 

 

Note that we have used Gomega’s Expect syntax to create expectations on the ContinentNameByPopulation() method.

 

Now that we have described the Continent model and its behavior, run the tests. This will yield the following result:

 

$ ginkgo #or go test, whichever suits you
=== RUN TestBootstrap

Running Suite: Continents Suite
=========================
Random Seed: 1278938274

Will run 2 of 2 specs

••
Ran 2 of 2 Specs in 0.000 seconds
SUCCESS! — 2 Passed | 0 Failed | 0 Pending | 0 Skipped

— PASS: TestBootstrap (0.00 seconds)
PASS
ok      continents   0.02s

 

Grouping of Specs using Context()

 

Instead of giving a single spec within the Context(), we can add a group of specs within it. To do so, we need to add the individual spec within an It block, which in turn is placed within a Describe or Context container block. Check out the following example:

 


var _ = Describe("Continent", func() {
   var (
       continent Continent
       err error
   )
   BeforeEach(func() {
       continent, err = BiggestContinentFromJSON(`{
           "name":"Asia",
           "population":"4.4 billion people",
           "countries":48
       }`)
   })

   Describe("loading from JSON", func() {
       Context("when the JSON parses successfully", func() {
           It("should populate the fields correctly", func() {
               Expect(continent.Name).To(Equal("Asia"))
               Expect(continent.Population).To(Equal("4.4 billion people"))
               Expect(continent.Countries).To(Equal(48))
           })

           It("should not error", func() {
               Expect(err).NotTo(HaveOccurred())
           })
       })
})

 

 

 

This example differs from the previous one in the point that in this example, both the positive and negative context of the test case are mentioned as separate It blocks within the same Context(). Testing using Ginkgo is quite similar to using the RSpec in Ruby on Rails, wherein it uses a structure quite similar to the RSpec, and anyone familiar with Rspec can relate to this easily.

 

Anyways, this is how we use the testing framework in Golang, and perform testing using Ginkgo in Golang.