Skip to content

muonsoft/api-testing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

API testing tools for Golang

Go Reference GitHub go.mod Go version GitHub release (latest by date) GitHub Go Report Card CI

apitest package

The apitest package provides methods for testing client-server communication. It can be used to test http.Handler to build complex assertions on the HTTP responses.

Example

package yours

import (
    "net/http"
    "testing"
    "github.com/muonsoft/api-testing/apitest"
    "github.com/muonsoft/api-testing/assertjson"
)

func TestYourAPI(t *testing.T) {
    handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        writer.WriteHeader(http.StatusOK)
        writer.Header().Set("Content-Type", "application/json")
        writer.Write([]byte(`{"ok":true}`))
    })
    
    // HandleGET builds and sends GET request to handler
    response := apitest.HandleGET(t, handler, "/example")
    
    response.IsOK()
    response.HasContentType("application/json")
    response.HasJSON(func(json *assertjson.AssertJSON) {
        json.Node("ok").IsTrue()
    })
    response.Print() // prints response with headers and body
    response.PrintJSON() // prints response with headers and indented JSON body
}

assertjson package

The assertjson package provides methods for testing JSON values. Selecting JSON values provided by JSON Pointer Syntax.

Example

package yours

import (
    "fmt"
    "net/http/httptest"
    "testing"
    "time"

    "github.com/gofrs/uuid"
    "github.com/golang-jwt/jwt/v4"
    "github.com/muonsoft/api-testing/assertjson"
    "github.com/stretchr/testify/assert"
)

func TestYourAPI(t *testing.T) {
    recorder := httptest.NewRecorder()
    handler := newHTTPHandler()

    request := httptest.NewRequest("GET", "/content", nil)
    handler.ServeHTTP(recorder, request)

    assertjson.Has(t, recorder.Body.Bytes(), func(json *assertjson.AssertJSON) {
        // common assertions
        json.Node("nullNode").Exists()
        json.Node("notExistingNode").DoesNotExist()
        json.Node("nullNode").IsNull()
        json.Node("stringNode").IsNotNull()
        json.Node("trueBooleanNode").IsTrue()
        json.Node("falseBooleanNode").IsFalse()
        json.Node("objectNode").EqualJSON(`{"objectKey": "objectValue"}`)

        // string assertions
        json.Node("stringNode").IsString()
        json.Node("stringNode").Matches("^string.*$")
        json.Node("stringNode").DoesNotMatch("^notMatch$")
        json.Node("stringNode").Contains("string")
        json.Node("stringNode").DoesNotContain("notContain")

        // fluent string assertions
        json.Node("stringNode").IsString()
        json.Node("emptyString").IsString().IsEmpty()
        json.Node("stringNode").IsString().IsNotEmpty()
        json.Node("stringNode").IsString().EqualTo("stringValue")
        json.Node("stringNode").IsString().EqualToOneOf("stringValue", "nextValue")
        json.Node("stringNode").IsString().NotEqualTo("invalid")
        json.Node("stringNode").IsString().Matches("^string.*$")
        json.Node("stringNode").IsString().NotMatches("^notMatch$")
        json.Node("stringNode").IsString().Contains("string")
        json.Node("stringNode").IsString().NotContains("notContain")
        json.Node("stringNode").IsString().WithLength(11)
        json.Node("stringNode").IsString().WithLengthGreaterThan(10)
        json.Node("stringNode").IsString().WithLengthGreaterThanOrEqual(11)
        json.Node("stringNode").IsString().WithLengthLessThan(12)
        json.Node("stringNode").IsString().WithLengthLessThanOrEqual(11)
        json.Node("stringNode").IsString().That(func(s string) error {
            if s != "stringValue" {
                return fmt.Errorf("invalid")
            }
            return nil
        })
        json.Node("stringNode").IsString().Assert(func(t testing.TB, value string) {
            assert.Equal(t, "stringValue", value)
        })

        // numeric assertions
        json.Node("integerNode").IsInteger()
        json.Node("zeroInteger").IsInteger().IsZero()
        json.Node("integerNode").IsInteger().IsNotZero()
        json.Node("integerNode").IsInteger().EqualTo(123)
        json.Node("integerNode").IsInteger().NotEqualTo(321)
        json.Node("integerNode").IsInteger().GreaterThan(122)
        json.Node("integerNode").IsInteger().GreaterThanOrEqual(123)
        json.Node("integerNode").IsInteger().LessThan(124)
        json.Node("integerNode").IsInteger().LessThanOrEqual(123)
        json.Node("floatNode").IsFloat()
        json.Node("floatNode").IsNumber()
        json.Node("zeroFloat").IsNumber().IsZero()
        json.Node("floatNode").IsNumber().IsNotZero()
        json.Node("floatNode").IsNumber().EqualTo(123.123)
        json.Node("floatNode").IsNumber().NotEqualTo(321.123)
        json.Node("floatNode").IsNumber().EqualToWithDelta(123.123, 0.1)
        json.Node("floatNode").IsNumber().GreaterThan(122)
        json.Node("floatNode").IsNumber().GreaterThanOrEqual(123.123)
        json.Node("floatNode").IsNumber().LessThan(124)
        json.Node("floatNode").IsNumber().LessThanOrEqual(123.123)
        json.Node("floatNode").IsNumber().GreaterThanOrEqual(122).LessThanOrEqual(124)

        // string values assertions
        json.Node("uuid").IsString().WithUUID()
        json.Node("uuid").IsUUID().IsNotNil().OfVersion(4).OfVariant(1)
        json.Node("uuid").IsUUID().EqualTo(uuid.FromStringOrNil("23e98a0c-26c8-410f-978f-d1d67228af87"))
        json.Node("uuid").IsUUID().NotEqualTo(uuid.FromStringOrNil("a54cbd42-b30c-4619-b89a-47375734d49c"))
        json.Node("nilUUID").IsUUID().IsNil()
        json.Node("email").IsEmail()
        json.Node("email").IsHTML5Email()
        json.Node("url").IsURL().WithSchemas("https").WithHosts("example.com")

        // time assertions
        json.Node("time").IsTime().EqualTo(time.Date(2022, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().NotEqualTo(time.Date(2021, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().AfterOrEqualTo(time.Date(2022, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().After(time.Date(2021, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().Before(time.Date(2023, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().BeforeOrEqualTo(time.Date(2022, time.October, 16, 12, 14, 32, 0, time.UTC))
        json.Node("time").IsTime().AtDate(2022, time.October, 16)
        json.Node("date").IsDate().EqualToDate(2022, time.October, 16)
        json.Node("date").IsDate().NotEqualToDate(2021, time.October, 16)
        json.Node("date").IsDate().AfterDate(2021, time.October, 16)
        json.Node("date").IsDate().AfterOrEqualToDate(2022, time.October, 16)
        json.Node("date").IsDate().BeforeDate(2023, time.October, 16)
        json.Node("date").IsDate().BeforeOrEqualToDate(2022, time.October, 16)

        // array assertions
        json.Node("arrayNode").IsArray()
        json.Node("arrayNode").IsArray().WithLength(1)
        json.Node("arrayNode").IsArray().WithLengthGreaterThan(0)
        json.Node("arrayNode").IsArray().WithLengthGreaterThanOrEqual(1)
        json.Node("arrayNode").IsArray().WithLengthLessThan(2)
        json.Node("arrayNode").IsArray().WithLengthLessThanOrEqual(1)
        json.Node("arrayNode").IsArray().WithUniqueElements()
        json.Node("arrayNode").ForEach(func(node *assertjson.AssertNode) {
            node.IsString().EqualTo("arrayValue")
        })

        // object assertions
        json.Node("objectNode").IsObject()
        json.Node("objectNode").IsObject().WithPropertiesCount(1)
        json.Node("objectNode").IsObject().WithPropertiesCountGreaterThan(0)
        json.Node("objectNode").IsObject().WithPropertiesCountGreaterThanOrEqual(1)
        json.Node("objectNode").IsObject().WithPropertiesCountLessThan(2)
        json.Node("objectNode").IsObject().WithPropertiesCountLessThanOrEqual(1)
        json.Node("objectNode").IsObject().WithUniqueElements()
        json.Node("objectNode").ForEach(func(node *assertjson.AssertNode) {
            node.IsString().EqualTo("objectValue")
        })

        // seek node by path elements
        json.Node("bookstore", "books", 1, "name").IsString().EqualTo("Green book")

        // use fmt.Stringer in node path
        id := uuid.FromStringOrNil("9b1100ea-986b-446b-ae7e-0c8ce7196c26")
        json.Node("hashmap", id, "key").IsString().EqualTo("value")

        // complex keys
        json.Node("@id").IsString().EqualTo("json-ld-id")
        json.Node("hydra:members").IsString().EqualTo("hydraMembers")

        // reusable assertions
        isGreenBook := func(json *assertjson.AssertJSON) {
            json.Node("id").IsInteger().EqualTo(123)
            json.Node("name").IsString().EqualTo("Green book")
        }
        json.Node("bookstore", "books", 1).Assert(isGreenBook)
        json.Node("bookstore", "bestBook").Assert(isGreenBook)
        isGreenBook(json.At("bookstore", "books", 1))
        isGreenBook(json.At("bookstore", "bestBook"))

        // JSON Web Token (JWT) assertion
        isJWT := json.Node("jwt").IsJWT(func(token *jwt.Token) (interface{}, error) {
            return []byte("your-256-bit-secret"), nil
        })
        isJWT.
            WithAlgorithm("HS256").
            // standard claims assertions
            WithID("abc12345").
            WithIssuer("https://issuer.example.com").
            WithSubject("https://subject.example.com").
            WithAudience([]string{"https://audience1.example.com", "https://audience2.example.com"}).
            // json assertion of header part
            WithHeader(func(json *assertjson.AssertJSON) {
                json.Node("alg").IsString().EqualTo("HS256")
                json.Node("typ").IsString().EqualTo("JWT")
            }).
            // json assertion of payload part
            WithPayload(func(json *assertjson.AssertJSON) {
                json.Node("name").IsString().EqualTo("John Doe")
            })
        // time assertions for standard claims
        isJWT.WithExpiresAt().AfterDate(2022, time.October, 26)
        isJWT.WithNotBefore().BeforeDate(2022, time.October, 27)
        isJWT.WithIssuedAt().BeforeDate(2022, time.October, 27)

        // get node values
        assert.Equal(t, "stringValue", json.Node("stringNode").Value())
        assert.Equal(t, "stringValue", json.Node("stringNode").String())
        assert.Equal(t, "123", json.Node("integerNode").String())
        assert.Equal(t, "123.123000", json.Node("floatNode").String())
        assert.Equal(t, 123.0, json.Node("integerNode").Float())
        assert.Equal(t, 123.123, json.Node("floatNode").Float())
        assert.Equal(t, 123, json.Node("integerNode").Integer())
        assert.Equal(t, 1, json.Node("arrayNode").IsArray().Length())
        assert.Equal(t, 1, json.Node("arrayNode").ArrayLength())
        assert.Equal(t, 1, json.Node("objectNode").IsObject().PropertiesCount())
        assert.Equal(t, 1, json.Node("objectNode").ObjectPropertiesCount())
        assert.JSONEq(t, `{"objectKey": "objectValue"}`, string(json.Node("objectNode").JSON()))
        assert.Equal(t, "2022-10-16T15:14:32+03:00", json.Node("time").Time().Format(time.RFC3339))
        assert.Equal(t, "23e98a0c-26c8-410f-978f-d1d67228af87", json.Node("uuid").IsUUID().Value().String())
        assert.Equal(t, "23e98a0c-26c8-410f-978f-d1d67228af87", json.Node("uuid").UUID().String())

        // standalone JWT assertion
        assertjson.IsJWT(t,
            "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hdWRpZW5jZTEuZXhhbXBsZS5jb20iLCJodHRwczovL2F1ZGllbmNlMi5leGFtcGxlLmNvbSJdLCJleHAiOjQ4MjAzNjAxMzEsImlhdCI6MTY2Njc1NjUzMSwiaXNzIjoiaHR0cHM6Ly9pc3N1ZXIuZXhhbXBsZS5jb20iLCJqdGkiOiJhYmMxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsIm5iZiI6MTY2Njc1NjUzMSwic3ViIjoiaHR0cHM6Ly9zdWJqZWN0LmV4YW1wbGUuY29tIn0.fGUvIn-BV8bPKkZdrxUneew3_qBe-knptL9a_TkNA4M",
            func(token *jwt.Token) (interface{}, error) { return []byte("your-256-bit-secret"), nil },
        ).WithPayload(func(json *assertjson.AssertJSON) {
            json.Node("name").IsString().EqualTo("John Doe")
        })

        // debug helpers
        json.Node("bookstore", "books", 1).Print()
    })
}

assertxml package

The assertjson package provides methods for testing XML values. Selecting XML values provided by XML Path Syntax.

Example

package yours

import (
    "net/http"
    "net/http/httptest"
    "testing"
    "github.com/muonsoft/api-testing/assertxml"
)

func TestYourAPI(t *testing.T) {
    recorder := httptest.NewRecorder()
    handler := createHTTPHandler()

    request, _ := http.NewRequest("GET", "/content", nil)
    handler.ServeHTTP(recorder, request)

    assertxml.Has(t, recorder.Body.Bytes(), func(xml *AssertXML) {
        // common assertions
        xml.Node("/root/stringNode").Exists()
        xml.Node("/root/notExistingNode").DoesNotExist()
  
        // string assertions
        xml.Node("/root/stringNode").EqualToTheString("stringValue")
    })
}