dbsteps

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jan 26, 2022 License: MIT Imports: 17 Imported by: 2

README

Cucumber database steps for Go

Build Status Coverage Status GoDevDoc Time Tracker Code lines Comments

This module implements database-related step definitions for github.com/cucumber/godog.

Database Configuration

Databases instances should be configured with Manager.Instances.

// Initialize database manager with storage and table rows references.
dbm := dbsteps.NewManager()
dbm.Instances["my_db"] = dbsteps.Instance{
    Storage: storage,
    Tables: map[string]interface{}{
        "my_table":         new(repository.MyRow),
        "my_another_table": new(repository.MyAnotherRow),
    },
    // Optionally configure statements to execute after deleting rows from table.
    PostCleanup: map[string][]string{
        "my_table": {"ALTER SEQUENCE my_table_id_seq RESTART"},
    },
}

Row types should be structs with db field tags, for example:

type MyRow struct {
    ID   int    `db:"id"`
    Name string `db:"name"`
}

These structures are used to map data between database and gherkin tables.

Table Mapper Configuration

Table mapper allows customizing decoding string values from godog table cells into Go row structures and back.

tableMapper := dbsteps.NewTableMapper()

// Apply JSON decoding to a particular type.
tableMapper.Decoder.RegisterFunc(func(s string) (interface{}, error) {
    m := repository.Meta{}
    err := json.Unmarshal([]byte(s), &m)
    if err != nil {
        return nil, err
    }
	
    return m, err
}, repository.Meta{})

// Apply string splitting to github.com/lib/pq.StringArray.
tableMapper.Decoder.RegisterFunc(func(s string) (interface{}, error) {
    return pq.StringArray(strings.Split(s, ",")), nil
}, pq.StringArray{})

// Create database manager with custom mapper.
dbm := dbsteps.NewManager()
dbm.TableMapper = tableMapper

Step Definitions

Delete all rows from table.

Given there are no rows in table "my_table" of database "my_db"

Populate rows in a database.

And these rows are stored in table "my_table" of database "my_db"
| id | foo   | bar | created_at           | deleted_at           |
| 1  | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
| 2  | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
| 3  | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |
And rows from this file are stored in table "my_table" of database "my_db"
 """
 path/to/rows.csv
 """

Assert rows existence in a database.

For each row in gherkin table database is queried to find a row with WHERE condition that includes provided column values.

If a column has NULL value, it is excluded from WHERE condition.

Column can contain variable (any unique string starting with $ or other prefix configured with Manager.VarPrefix). If variable has not yet been populated, it is excluded from WHERE condition and populated with value received from database. When this variable is used in next steps, it replaces the value of column with value of variable.

Variables can help to assert consistency of dynamic data, for example variable can be populated as ID of one entity and then checked as foreign key value of another entity. This can be especially helpful in cases of UUIDs.

If column value represents JSON array or object it is excluded from WHERE condition, value assertion is done by comparing Go value mapped from database row field with Go value mapped from gherkin table cell.

Then these rows are available in table "my_table" of database "my_db"
| id   | foo   | bar | created_at           | deleted_at           |
| $id1 | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
| $id2 | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
| $id3 | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |
Then rows from this file are available in table "my_table" of database "my_db"
 """
 path/to/rows.csv
 """

It is possible to check table contents exhaustively by adding "only" to step statement. Such assertion will also make sure that total number of rows in database table matches number of rows in gherkin table.

Then only these rows are available in table "my_table" of database "my_db"
| id   | foo   | bar | created_at           | deleted_at           |
| $id1 | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
| $id2 | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
| $id3 | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |
Then only rows from this file are available in table "my_table" of database "my_db"
 """
 path/to/rows.csv
 """

Assert no rows exist in a database.

And no rows are available in table "my_another_table" of database "my_db"

The name of database instance of database "my_db" can be omitted in all steps, in such case "default" will be used from database instance name.

Concurrent Usage

Please note, due to centralized nature of database instance, scenarios that work with same tables would conflict. In order to avoid conflicts, dbsteps locks access to a specific scenario until that scenario is finished. The lock is per table, so if scenarios are operating on different tables, they will not conflict. It is safe to use concurrent scenarios.

Documentation

Overview

Package dbsteps provides godog steps to handle database state.

Database Configuration

Databases instances should be configured with Manager.Instances.

dbm := dbsteps.Manager{}

dbm.Instances = map[string]dbsteps.Instance{
	"my_db": {
		Storage: storage,
		Tables: map[string]interface{}{
			"my_table":           new(repository.MyRow),
			"my_another_table":   new(repository.MyAnotherRow),
		},
	},
}

Table TableMapper Configuration

Table mapper allows customizing decoding string values from godog table cells into Go row structures and back.

tableMapper := dbsteps.NewTableMapper()

// Apply JSON decoding to a particular type.
tableMapper.Decoder.RegisterFunc(func(s string) (interface{}, error) {
	m := repository.Meta{}
	err := json.Unmarshal([]byte(s), &m)
	if err != nil {
		return nil, err
	}
	return m, err
}, repository.Meta{})

// Apply string splitting to github.com/lib/pq.StringArray.
tableMapper.Decoder.RegisterFunc(func(s string) (interface{}, error) {
	return pq.StringArray(strings.Split(s, ",")), nil
}, pq.StringArray{})

// Create database manager with custom mapper.
dbm := dbsteps.Manager{
	TableMapper: tableMapper,
}

Step Definitions

Delete all rows from table.

Given there are no rows in table "my_table" of database "my_db"

Populate rows in a database with a gherkin table.

	   And these rows are stored in table "my_table" of database "my_db"
		 | id | foo   | bar | created_at           | deleted_at           |
		 | 1  | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
		 | 2  | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
		 | 3  | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |

 Or with an CSV file

	   And rows from this file are stored in table "my_table" of database "my_db"
		 """
		 path/to/rows.csv
		 """

Assert rows existence in a database.

For each row in gherkin table DB is queried to find a row with WHERE condition that includes provided column values.

If a column has NULL value, it is excluded from WHERE condition.

Column can contain variable (any unique string starting with $ or other prefix configured with Manager.VarPrefix). If variable has not yet been populated, it is excluded from WHERE condition and populated with value received from database. When this variable is used in next steps, it replaces the value of column with value of variable.

Variables can help to assert consistency of dynamic data, for example variable can be populated as ID of one entity and then checked as foreign key value of another entity. This can be especially helpful in cases of UUIDs.

If column value represents JSON array or object it is excluded from WHERE condition, value assertion is done by comparing Go value mapped from database row field with Go value mapped from gherkin table cell.

   Then these rows are available in table "my_table" of database "my_db"
	 | id   | foo   | bar | created_at           | deleted_at           |
	 | $id1 | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
	 | $id2 | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
	 | $id3 | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |

Rows can be also loaded from CSV file.

   Then rows from this file are available in table "my_table" of database "my_db"
	 """
	 path/to/rows.csv
	 """

It is possible to check table contents exhaustively by adding "only" to step statement. Such assertion will also make sure that total number of rows in database table matches number of rows in gherkin table.

   Then only these rows are available in table "my_table" of database "my_db"
	 | id   | foo   | bar | created_at           | deleted_at           |
	 | $id1 | foo-1 | abc | 2021-01-01T00:00:00Z | NULL                 |
	 | $id2 | foo-1 | def | 2021-01-02T00:00:00Z | 2021-01-03T00:00:00Z |
	 | $id3 | foo-2 | hij | 2021-01-03T00:00:00Z | 2021-01-03T00:00:00Z |

Rows can be also loaded from CSV file.

   Then only rows from this file are available in table "my_table" of database "my_db"
	 """
	 path/to/rows.csv
	 """

Assert no rows exist in a database.

And no rows are available in table "my_another_table" of database "my_db"

Index

Examples

Constants

View Source
const Default = "default"

Default is the name of default database.

Variables

This section is empty.

Functions

func ParseTime

func ParseTime(s string, formats ...string) (time.Time, error)

ParseTime tries to parse time in multiple formats.

func Rows

func Rows(data *godog.Table) [][]string

Rows converts godog table to a nested slice of strings.

Types

type Instance

type Instance struct {
	Storage *sqluct.Storage
	// Tables is a map of row structures per table name.
	// Example: `"my_table": new(MyEntityRow)`
	Tables map[string]interface{}
	// PostNoRowsStatements is a map of SQL statement list per table name.
	// They are executed after `no rows in table` step.
	// Example: `"my_table": []string{"ALTER SEQUENCE my_table_id_seq RESTART"}`.
	PostCleanup map[string][]string
	// contains filtered or unexported fields
}

Instance provides database instance.

type IterateConfig

type IterateConfig struct {
	Data       [][]string
	SkipDecode func(column, value string) bool
	Item       interface{}
	Replaces   map[string]string
	ReceiveRow func(index int, row interface{}, colNames []string, rawValues []string) error
}

IterateConfig controls behavior of TableMapper.IterateTable.

type Manager

type Manager struct {
	TableMapper *TableMapper
	Instances   map[string]Instance

	// Vars allow sharing vars with other steps.
	Vars *shared.Vars
	// contains filtered or unexported fields
}

Manager owns database connections.

Please use NewManager to create an instance.

func NewManager

func NewManager() *Manager

NewManager creates an instance of database Manager.

Example
package main

import (
	"github.com/bool64/sqluct"
	"github.com/godogx/dbsteps"
)

func main() {
	// Set up a storage adapter for your database connection.
	var storage *sqluct.Storage

	// Define database row structures in your repository packages.
	type MyRow struct {
		Foo string `db:"foo"`
	}

	type MyAnotherRow struct {
		Bar int     `db:"bar"`
		Baz float64 `db:"baz"`
	}

	// ...........

	// Initialize database manager with storage and table rows references.
	dbm := dbsteps.NewManager()
	dbm.Instances["my_db"] = dbsteps.Instance{
		Storage: storage,
		Tables: map[string]interface{}{
			"my_table":         new(MyRow),
			"my_another_table": new(MyAnotherRow),
		},
		// Optionally configure statements to execute after deleting rows from table.
		PostCleanup: map[string][]string{
			"my_table": {"ALTER SEQUENCE my_table_id_seq RESTART"},
		},
	}
}
Output:

func (*Manager) RegisterJSONTypes

func (m *Manager) RegisterJSONTypes(values ...interface{})

RegisterJSONTypes registers types of provided values to unmarshal as JSON when decoding from string.

Arguments should match types of fields in row entities. If field is a pointer, argument should be a pointer: e.g. new(MyType). If field is not a pointer, argument should not be a pointer: e.g. MyType{}.

func (*Manager) RegisterSteps

func (m *Manager) RegisterSteps(s *godog.ScenarioContext)

RegisterSteps adds database manager context to test suite.

type TableMapper

type TableMapper struct {
	Decoder *form.Decoder
	Encoder *form.Encoder
}

TableMapper maps data from Go value to string and back.

func NewTableMapper

func NewTableMapper() *TableMapper

NewTableMapper creates tablestruct.TableMapper with db field decoder.

Example
package main

import (
	"encoding/json"

	"github.com/godogx/dbsteps"
)

func main() {
	type jsonData struct {
		Foo string `json:"foo"`
	}

	tableMapper := dbsteps.NewTableMapper()

	// Apply JSON decoding to a particular type.
	tableMapper.Decoder.RegisterFunc(func(s string) (interface{}, error) {
		data := jsonData{}
		err := json.Unmarshal([]byte(s), &data)
		if err != nil {
			return nil, err
		}

		return data, err
	}, jsonData{})

	// Create database manager with custom mapper.
	dbm := dbsteps.NewManager()
	dbm.TableMapper = tableMapper
}
Output:

func (*TableMapper) Encode

func (m *TableMapper) Encode(v interface{}) (string, error)

Encode converts Go value to string.

func (*TableMapper) IterateTable

func (m *TableMapper) IterateTable(c IterateConfig) error

IterateTable walks gherkin table calling row receiver with mapped row. If receiver returns error iteration stops and error is propagated.

func (*TableMapper) SliceFromTable

func (m *TableMapper) SliceFromTable(data [][]string, item interface{}) (interface{}, error)

SliceFromTable creates a slice from gherkin table, item type is used as slice element type.

Jump to

Keyboard shortcuts

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