Introducing Agouti – A Golang Acceptance Testing Framework

November 4, 2014 Stephen Levine

Ever wish you could write acceptance or integration tests for your Go-based web app without bringing in Capybara? Allow me to introduce:

agouti

Agouti lets you write acceptance tests using Ginkgo and Gomega. It provides a clean interface for controlling and making assertions against a browser-based web service. It supports Selenium WebDriver, PhantomJS, and ChromeDriver, allowing you to run your tests headlessly or in any major browser. It also supports Ginkgo’s parallelization capabilities, allowing you to run your integration tests in parallel for quicker execution.

Although Agouti is intended to be used with Ginkgo and Gomega, Agouti’s core package is a general-purpose WebDriver API for Go that does not depend on either of them. It is even possible to use Agouti and Gomega together without Ginkgo.

Check it out at agouti.org and on Github!

Want to get started right now? Go get Agouti, install your choice of WebDriver, and use Ginkgo to bootstrap your package:

$ go get github.com/sclevine/agouti 
$ brew install phantomjs
$ cd path/to/yourpackage
$ ginkgo bootstrap --agouti

Let’s generate a template for a simple user login test:

$ ginkgo generate --agouti user_login

Now open it up in your favorite editor:

package yourpackage_test

import (
    . "path/to/yourpackage"

    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    . "github.com/sclevine/agouti/core"
)

var _ = Describe("UserLogin", func() {
    var page Page

    BeforeEach(func() {
        var err error
        page, err = agoutiDriver.Page()
        Expect(err).NotTo(HaveOccurred())
    })

    AfterEach(func() {
        page.Destroy()
    })
})

And start writing some integration specs!

package yourpackage_test

import (
    . "path/to/yourpackage"

    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    . "github.com/sclevine/agouti/core"
)

var _ = Describe("User Login", func() {
    var page Page

    BeforeEach(func() {
        var err error
        page, err = agoutiDriver.Page()
        Expect(err).NotTo(HaveOccurred())
    })

    AfterEach(func() {
        page.Destroy()
    })

    It("should manage user authentication", func() {
        By("redirecting the user to the login form", func() {
            Expect(page.Navigate("http://localhost:3000")).To(Succeed())
            Expect(page).To(HaveURL("http://localhost:3000/login"))
        })

        By("allowing the user to fill out the login form", func() {
            userField := page.FindByLabel("User")
            Eventually(userField).Fill("bob")).Should(Succeed())
            passwordField := page.FindByLabel("Password")
            Expect(passwordField.Fill("secret")).To(Succeed())
            Expect(page.FindByLabel("Remember Me").Check()).To(Succeed())
            Expect(page.Find("#login_form").Submit()).To(Succeed())
        })

        By("directing the user to the dashboard", func() {
            Eventually(page).Should(HaveTitle("Dashboard"))
        })

        By("allowing the user to view his or her profile", func() {
            Expect(page.FindByLink("Profile Page").Click()).To(Succeed())
            profile := page.Find("section.profile")
            greeting := profile.Find(".greeting")
            Eventually(greeting).Should(HaveText("Hello!"))
            Expect(profile.Find("img#profile_pic")).To(BeVisible())
        })

        By("allowing the user to log out", func() {
            Expect(page.Find("#logout").Click()).To(Succeed())
            Expect(page).To(HavePopupText("Are you sure?"))
            Expect(page.ConfirmPopup()).To(Succeed())
            Eventually(page).Should(HaveTitle("Login"))
        })
    })
})

(If the Expect(...).To(Succeed()) assertions seem a bit too verbose, or you like Capybara’s DSL, we have a package for that.)

About the Author

Biography

Previous
3 Key Capabilities Necessary for Text Analytics & Natural Language Processing in the Era of Big Data
3 Key Capabilities Necessary for Text Analytics & Natural Language Processing in the Era of Big Data

This post explains common, unstructured text processing tasks in detail so we can understand how they merge...

Next
A High-performing Mid-range NAS Server, Part 2: Performance Tuning for iSCSI
A High-performing Mid-range NAS Server, Part 2: Performance Tuning for iSCSI

This blog post describes how we tuned and benchmarked our FreeNAS fileserver for optimal iSCSI performance....