vars

package module
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2023 License: MIT Imports: 15 Imported by: 2

README

vars

Build Status Coverage Status GoDevDoc Time Tracker Code lines Comments

This library provides godog step definitions to manage variables shared between steps and API for other libraries.

Usage

Register steps to godog scenario context.

vs := vars.Steps{}
// You can add value generators that would be available with 'gen:' prefix, e.g. gen:new-id or gen:uuid.
vs.AddGenerator("new-id", func() (interface{}, error) {
    return 1337, nil
})

suite := godog.TestSuite{}
suite.ScenarioInitializer = func(s *godog.ScenarioContext) {
    vs.Register(s)
    
    // Other configuration and step definitions.
}

Use steps in feature files.

Feature: Variables

  Scenario: Setting and asserting variables
    # Assert variable have not been set.
    # Every variable name starts with var prefix, "$" by default.
    Given variable $foo is undefined
    # Set assign value to variable.
    # Every value is declared as JSON.
    When variable $foo is set to "abcdef"
    # Assert current value of variable.
    Then variable $foo equals to "abcdef"

    # Variable can be set with user-defined factory.
    When variable $userId is set to newUserID("$foo", addDuration(now(), "-10h"))
    Then variable $userId equals to 12321

    # Variable can be set with user-defined generator.
    When variable $foo is set to gen:new-id
    Then variable $foo equals to 1337

    # Set values to multiple variables.
    # Values are decoded into `any` with JSON decoder.
    # Beware that both integers and floats will be decoded as `float64`.
    # String values can interpolate other variables (see $replaced).
    # Values can be generated by user-defined functions (see $generated).
    When variables are set to values
      | $bar       | "abc"             |
      | $baz       | {"one":1,"two":2} |
      | $qux       | 123               |
      | $quux      | true              |
      | $replaced  | "$qux/test/$bar"  |
      | $generated | gen:new-id        |
    # Assert current values of multiple variables.
    # String values can interpolate other variables (see $replaced: "$qux/test/$bar").
    Then variables are equal to values
      | $bar       | "abc"             |
      | $baz       | {"one":1,"two":2} |
      | $qux       | 123               |
      | $quux      | true              |
      | $replaced  | "123/test/abc"    |
      | $replaced  | "$qux/test/$bar"  |
      | $generated | 1337              |
    And variable $qux equals to 123
    And variable $replaced equals to "$qux/test/$bar"
    And variable $replaced equals to "123/test/abc"

    When variable $bar is set to
    """json5
    // A JSON5 comment.
    {
      "foo":"$foo",
      "bar":12345,
      "baz":true,
      "prefixed_foo":"ooo::$foo"
    }
    """

    # Assert parts of variable value using JSON path.
    # This step can also be used to assign resolved JSON path to a new variable, see $collected.
    Then variable $bar matches JSON paths
      | $.foo          | 1337         |
      | $.bar          | 12345        |
      | $.bar          | "$collected" |
      | $.baz          | true         |
      | $.prefixed_foo | "ooo::$foo"  |
      | $.prefixed_foo | "ooo::1337"  |

    And variable $collected equals to 12345

Libraries can pass variables using context. For example httpsteps can set variable from API response and then dbsteps can use that value to query database.

// Libraries, that are unaware of each other, can use vars to communicate general state between themselves.
s.Step("^I do foo$", func(ctx context.Context) context.Context {
    return vars.ToContext(ctx, "$fooDone", true)
})

s.Step("^foo is done$", func(ctx context.Context) error {
    if done, ok := vars.FromContext(ctx)["$fooDone"]; ok {
        if b, ok := done.(bool); ok && b {
            return nil
        }
    }

    return errors.New("foo is not done")
})

    # Use vars in custom steps.
    When I do foo
    Then foo is done
Custom Steps

You can enable variables in your own step definitions with these contextualized helpers

  • Replace applies known vars to a byte slice,
  • ReplaceFile is same as Replace, but reads byte slice from a file,
  • Assert compares two byte slices, collects unknown vars, checks known vars,
  • AssertFile is same as Assert, but reads expected byte slice from a file,
  • AssertJSONPaths checks JSON byte slice against a godog.Table with expected values at JSON Paths.
Setting variable once for multiple scenarios and/or features

In some cases you may want to set a variable only once in the feature or globally (in all features).

This is handy if you want to reuse same resources across whole feature or suite.

You can use factory function to produce a singleton with a named variable, see ExampleSteps_AddFactory.

    Given variables are set to values once in this feature
      | $user1 | newUserID("John Doe", addDuration(now(), "-10h")) |
      | $user2 | newUserID("Jane Doe", addDuration(now(), "-20h")) |

    And variables are set to values once globally
      | $gv1 | gen:globalSeq |
      | $gv2 | gen:globalSeq |

Documentation

Overview

Package vars manages shared variables in godog gherkin tests.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Assert added in v0.1.6

func Assert(ctx context.Context, expected, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

Assert compares payloads and collects variables from JSON fields.

Example
package main

import (
	"context"
	"fmt"

	"github.com/godogx/vars"
)

func main() {
	// Variables are passed via chained context.
	ctx := context.Background()

	expected := []byte(`{"foo":"$foo","bar":123}`)
	received := []byte(`{"foo":321,"bar":123,"baz":true}`)

	// No error, $foo is populated with 321, "baz" is ignored for true `ignoreAddedJSONFields` argument.
	ctx, err := vars.Assert(ctx, expected, received, true)
	if err != nil {
		fmt.Println("assertion failed: " + err.Error())
	}

	expected = []byte(`{"foo":"$foo","bar":123,"prefixed_foo":"ooo::$foo"}`)
	received = []byte(`{"foo":313,"bar":123,"baz":true,"prefixed_foo":"ooo::321"}`)
	// Assertion fails.
	_, err = vars.Assert(ctx, expected, received, false)
	if err != nil {
		fmt.Println("assertion failed: " + err.Error())
	}

}
Output:

assertion failed: not equal:
 {
   "bar": 123,
-  "foo": 321,
+  "foo": 313,
   "prefixed_foo": "ooo::321"
+  "baz": true
 }

func AssertFile added in v0.1.6

func AssertFile(ctx context.Context, filePath string, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

AssertFile compares payloads and collects variables from JSON fields.

func AssertJSONPaths added in v0.1.6

func AssertJSONPaths(ctx context.Context, jsonPaths *godog.Table, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

AssertJSONPaths compares payload with a list of JSON path expectations.

func FromContext

func FromContext(ctx context.Context) map[string]interface{}

FromContext returns variables from context.

func Replace added in v0.1.6

func Replace(ctx context.Context, body []byte) (context.Context, []byte, error)

Replace replaces vars in bytes slice.

This function can help to interpolate variables into predefined templates. It is generally used to prepare `expected` value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/godogx/vars"
)

func main() {
	// Variables are passed via chained context.
	ctx := context.Background()

	ctx = vars.ToContext(ctx, "$foo", 321)

	expected := []byte(`{"foo":"$foo","bar":123, "prefixed_foo":"ooo::$foo"}`)

	_, expected, err := vars.Replace(ctx, expected)
	if err != nil {
		fmt.Println("replace failed: " + err.Error())
	}

	fmt.Println(string(expected))

}
Output:

{"foo":321,"bar":123,"prefixed_foo":"ooo::321"}

func ReplaceFile added in v0.1.6

func ReplaceFile(ctx context.Context, filePath string) (context.Context, []byte, error)

ReplaceFile replaces vars in file contents.

It works same as Replace using a file for input.

func ToContext

func ToContext(ctx context.Context, key string, value interface{}) context.Context

ToContext adds variable to context.

func Vars added in v0.1.5

func Vars(ctx context.Context) (context.Context, *shared.Vars)

Vars instruments context with a storage of variables.

Types

type Factory added in v0.1.7

type Factory func(ctx context.Context, args ...interface{}) (context.Context, interface{}, error)

Factory is a function to create variable value.

type Steps

type Steps struct {
	JSONComparer assertjson.Comparer
	// contains filtered or unexported fields
}

Steps provides godog gherkin step definitions.

func (*Steps) AddFactory added in v0.1.7

func (s *Steps) AddFactory(name string, f Factory)

AddFactory registers user-defined factory function, suitable for resource creation.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"time"

	"github.com/cucumber/godog"
	"github.com/godogx/vars"
)

func main() {
	vs := &vars.Steps{}

	vs.AddFactory("now", func(ctx context.Context, args ...interface{}) (context.Context, interface{}, error) {
		// "Now" is mocked with a constant value to reproducibility.
		return ctx, time.Date(2023, 5, 22, 19, 38, 0, 0, time.UTC), nil
	})

	vs.AddFactory("addDuration", func(ctx context.Context, args ...interface{}) (context.Context, interface{}, error) {
		if len(args) != 2 {
			return ctx, nil, errors.New("addDuration expects 2 arguments: base time, duration")
		}
		var (
			base time.Time
			dur  time.Duration
		)

		switch v := args[0].(type) {
		case time.Time:
			base = v
		case string:
			t, err := time.Parse(time.RFC3339Nano, v)
			if err != nil {
				return ctx, nil, fmt.Errorf("parsing base time: %w", err)
			}
			base = t
		default:
			return ctx, nil, fmt.Errorf("unexpected type %T for base time, string or time.Time expected", v)
		}

		switch v := args[1].(type) {
		case time.Duration:
			dur = v
		case string:
			d, err := time.ParseDuration(v)
			if err != nil {
				return ctx, nil, fmt.Errorf("parsing duration: %w", err)
			}
			dur = d
		default:
			return ctx, nil, fmt.Errorf("unexpected type %T for duration, string or time.Duration expected", v)
		}

		return ctx, base.Add(dur), nil
	})

	vs.AddFactory("newUserID", func(ctx context.Context, args ...interface{}) (context.Context, interface{}, error) {
		if len(args) != 2 {
			return ctx, nil, errors.New("newUserID expects 2 arguments: name, registeredAt")
		}
		var (
			name         string
			registeredAt time.Time
		)

		switch v := args[0].(type) {
		case string:
			name = v
		default:
			return ctx, nil, fmt.Errorf("unexpected type %T for name, string expected", v)
		}

		switch v := args[1].(type) {
		case time.Time:
			registeredAt = v
		case string:
			t, err := time.Parse(time.RFC3339Nano, v)
			if err != nil {
				return ctx, nil, fmt.Errorf("parsing registeredAt: %w", err)
			}
			registeredAt = t
		default:
			return ctx, nil, fmt.Errorf("unexpected type %T for registeredAt, string or time.Time expected", v)
		}

		fmt.Println("creating user", name, registeredAt)

		// Return relevant value, for example user id.
		return ctx, 123, nil
	})

	s := godog.TestSuite{}

	s.ScenarioInitializer = func(sc *godog.ScenarioContext) {
		vs.Register(sc)
	}

	s.Options = &godog.Options{
		Format: "pretty",
		Output: io.Discard,
		FeatureContents: []godog.Feature{
			{
				Name: "example",
				Contents: []byte(`
Feature: example
Scenario: using var factory
   Given variable $myUserID is set to newUserID("John Doe", addDuration(now(), "-10h"))
`),
			},
		},
	}

	s.Run()

}
Output:

creating user John Doe 2023-05-22 09:38:00 +0000 UTC

func (*Steps) AddGenerator added in v0.1.4

func (s *Steps) AddGenerator(name string, f func() (interface{}, error))

AddGenerator registers user-defined generator function, suitable for random identifiers.

func (*Steps) Assert added in v0.1.5

func (s *Steps) Assert(ctx context.Context, expected, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

Assert compares payloads and collects variables from JSON fields.

func (*Steps) AssertFile added in v0.1.5

func (s *Steps) AssertFile(ctx context.Context, filePath string, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

AssertFile compares payloads and collects variables from JSON fields.

func (*Steps) AssertJSONPaths added in v0.1.5

func (s *Steps) AssertJSONPaths(ctx context.Context, jsonPaths *godog.Table, received []byte, ignoreAddedJSONFields bool) (context.Context, error)

AssertJSONPaths compares payload with a list of JSON path expectations.

func (*Steps) Register

func (s *Steps) Register(sc *godog.ScenarioContext)

Register add steps to scenario context.

func (*Steps) Replace added in v0.1.5

func (s *Steps) Replace(ctx context.Context, body []byte) (context.Context, []byte, error)

Replace replaces vars in bytes slice.

This function can help to interpolate variables into predefined templates. It is generally used to prepare `expected` value.

func (*Steps) ReplaceFile added in v0.1.5

func (s *Steps) ReplaceFile(ctx context.Context, filePath string) (context.Context, []byte, error)

ReplaceFile replaces vars in file contents.

It works same as Replace using a file for input.

func (*Steps) Vars added in v0.1.5

func (s *Steps) Vars(ctx context.Context) (context.Context, *shared.Vars)

Vars instruments context with a storage of variables.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL