Repository: go-bdd/gobdd
Pull request #64 (2020-02-04 13:27:56)

Add context
Fixes https://github.com/go-bdd/gobdd/issues/57
FYI @sagikazarmark 

sagikazarmark: Would be nice to have some sort of `ctx.Unmarshal("mycontext", &myctx)`, so that we can avoid type assertions in the test code.
bkielbasa: we can discuss about it in another proposal :)

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3ab4ee9..20665c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,6 +27,9 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v2
 
+      - name: go generate
+        run: go generate
+
       - name: Run tests
         run: go test ./...
 
diff --git a/context/context.go b/context/context.go
new file mode 100644
index 0000000..e17826e
--- /dev/null
+++ b/context/context.go
@@ -0,0 +1,36 @@
+package context
+
+import (
+	"fmt"
+)
+
+// Holds data from previously executed steps
+type Context struct {
+	values map[interface{}]interface{}
+}
+
+// Creates a new (empty) context struct
+func New() Context {
+	return Context{
+		values: map[interface{}]interface{}{},
+	}
+}
+
+// Sets the value under the key
+func (ctx Context) Set(key interface{}, value interface{}) {
+	ctx.values[key] = value
+}
+
+// Returns the data under the key.
+// If couldn't find anything but the default value is provided, returns the default value.
+// Otherwise, it panics.
+func (ctx Context) Get(key interface{}, defaultValue ...interface{}) interface{} {
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	return ctx.values[key]
+}
diff --git a/context/doc.go b/context/doc.go
new file mode 100644
index 0000000..53cbc01
--- /dev/null
+++ b/context/doc.go
@@ -0,0 +1,11 @@
+// The context is created to hold all the required data from previous steps to be reused.
+// Its goal preventing from holding a global state if it's not needed what improves maintainability.
+// The context has a set of helper functions which simplify the end-user code.
+//  ctx.Set(name{}, "John")
+//  fmt.Printf("Hi %s\n", ctx.GetString(name{})) // prints "Hi John"
+//
+// Good practices
+//
+// It’s a good practice to use custom structs as keys instead of strings or any built-in types to avoid collisions between steps using context.
+
+package context
diff --git a/context/get.go b/context/get.go
new file mode 100644
index 0000000..647b6d8
--- /dev/null
+++ b/context/get.go
@@ -0,0 +1,176 @@
+// Code generated .* DO NOT EDIT.	
+package context
+
+import "fmt"
+
+func (ctx Context) GetString(key interface{}, defaultValue ...string) string {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(string)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not string (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetInt(key interface{}, defaultValue ...int) int {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(int)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not int (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetInt8(key interface{}, defaultValue ...int8) int8 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(int8)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not int8 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetInt16(key interface{}, defaultValue ...int16) int16 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(int16)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not int16 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetInt32(key interface{}, defaultValue ...int32) int32 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(int32)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not int32 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetInt64(key interface{}, defaultValue ...int64) int64 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(int64)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not int64 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetFloat32(key interface{}, defaultValue ...float32) float32 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(float32)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not float32 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetFloat64(key interface{}, defaultValue ...float64) float64 {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(float64)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not float64 (%T)", key))
+	}
+	return value
+}
+
+func (ctx Context) GetBool(key interface{}, defaultValue ...bool) bool {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].(bool)
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not bool (%T)", key))
+	}
+	return value
+}
+
diff --git a/gen.go b/gen.go
new file mode 100644
index 0000000..1b6f468
--- /dev/null
+++ b/gen.go
@@ -0,0 +1,3 @@
+//go:generate go run gen/ctxgen.go
+
+package gobdd
diff --git a/gen/ctxgen.go b/gen/ctxgen.go
new file mode 100644
index 0000000..5646de3
--- /dev/null
+++ b/gen/ctxgen.go
@@ -0,0 +1,161 @@
+package main
+
+import (
+	"html/template"
+	"log"
+	"os"
+	"strings"
+)
+
+type typeDef struct {
+	Name  string
+	Value string
+}
+
+func noescape(str string) template.HTML {
+	return template.HTML(str)
+}
+
+func main() {
+	funcMap := template.FuncMap{
+		"Title":    strings.Title,
+		"noescape": noescape,
+	}
+
+	types := []typeDef{
+		{
+			Name:  "string",
+			Value: `"example text"`,
+		},
+		{
+			Name:  "int",
+			Value: "123",
+		},
+		{
+			Name:  "int8",
+			Value: "123",
+		},
+		{
+			Name:  "int16",
+			Value: "123",
+		},
+		{
+			Name:  "int32",
+			Value: "123",
+		},
+		{
+			Name:  "int64",
+			Value: "123",
+		},
+		{
+			Name:  "float32",
+			Value: "123.5",
+		},
+		{
+			Name:  "float64",
+			Value: "123.5",
+		},
+		{
+			Name:  "bool",
+			Value: "false",
+		},
+	}
+
+	f, err := os.Create("context/get.go")
+	die(err)
+
+	var tmpl = template.Must(template.New("").Funcs(funcMap).Parse(getTmpl))
+
+	err = tmpl.Execute(f, struct {
+		Types []typeDef
+	}{Types: types})
+	die(err)
+	f.Close()
+
+	f, err = os.Create("context/get_test.go")
+	die(err)
+	tmpl = template.Must(template.New("").Funcs(funcMap).Parse(testTmpl))
+	err = tmpl.Execute(f, struct {
+		Types []typeDef
+	}{Types: types})
+	die(err)
+	f.Close()
+}
+
+func die(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+var testTmpl = `// Code generated .* DO NOT EDIT.	
+package context
+
+import "testing"
+{{ range .Types }}
+func TestContext_Get{{ .Name | Title }}(t *testing.T) {
+	ctx := New()
+	expected := {{ .Name }}({{ .Value | noescape }})
+	ctx.Set("test", expected)
+	received := ctx.Get{{ .Name | Title }}("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_Get{{ .Name | Title }}_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := {{ .Name }}({{ .Value | noescape }})
+	received := ctx.Get{{ .Name | Title }}("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_Get{{ .Name | Title }}_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the Get{{ .Name | Title }} should panic")
+        }
+    }()
+	_ = ctx.Get{{ .Name | Title }}("test", {{ .Value | noescape }}, {{ .Value | noescape }})
+}
+
+func TestContext_Get{{ .Name | Title }}_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the Get{{ .Name | Title }} should panic")
+        }
+    }()
+	_ = ctx.Get{{ .Name | Title }}("test")
+}
+{{ end }}	
+`
+
+var getTmpl = `// Code generated .* DO NOT EDIT.	
+package context
+
+import "fmt"
+{{ range .Types }}
+func (ctx Context) Get{{ .Name | Title }}(key interface{}, defaultValue ...{{ .Name }}) {{ .Name }} {
+	if len(defaultValue) > 1 {
+        panic(fmt.Sprintf("allowed to pass only 1 default value but %d got", len(defaultValue)))
+    }
+
+	if _, ok := ctx.values[key]; !ok {
+		if len(defaultValue) == 1 {
+			return defaultValue[0]
+		}
+		panic(fmt.Sprintf("the key %+v does not exist", key))
+	}
+
+	value, ok := ctx.values[key].({{ .Name }})
+	if !ok {
+		panic(fmt.Sprintf("the expected value is not {{ .Name }} (%T)", key))
+	}
+	return value
+}
+{{ end }}
+`
diff --git a/gobdd.go b/gobdd.go
index 47fd4ee..a7bc134 100644
--- a/gobdd.go
+++ b/gobdd.go
@@ -2,7 +2,6 @@ package gobdd
 
 import (
 	"bufio"
-	"context"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -17,6 +16,7 @@ import (
 
 	"github.com/cucumber/gherkin-go/v9"
 	"github.com/cucumber/messages-go/v9"
+	"github.com/go-bdd/gobdd/context"
 )
 
 // Holds all the information about the suite (options, steps to execute etc)
@@ -234,7 +234,7 @@ func (s *Suite) runFeature(feature *messages.GherkinDocument_Feature) error {
 				t.Log(fmt.Sprintf("Skipping scenario %s", scenario.Name))
 				continue
 			}
-			ctx := context.Background()
+			ctx := context.New()
 			err := s.runScenario(ctx, scenario, bkgSteps, t)
 			if err != nil {
 				hasErrors = true
diff --git a/steps.go b/steps.go
index c49a38a..92612b5 100644
--- a/steps.go
+++ b/steps.go
@@ -1,9 +1,10 @@
 package gobdd
 
 import (
-	"context"
 	"errors"
 	"reflect"
+
+	"github.com/go-bdd/gobdd/context"
 )
 
 func validateStepFunc(f interface{}) error {
@@ -16,13 +17,13 @@ func validateStepFunc(f interface{}) error {
 		return errors.New("the function should return the context.Context and error")
 	}
 	val := value.Type().Out(0)
-	contextInterface  := reflect.TypeOf((*context.Context)(nil)).Elem()
-	if !val.Implements(contextInterface) {
+	n := val.ConvertibleTo(reflect.TypeOf((*context.Context)(nil)).Elem())
+	if !n {
 		return errors.New("the returned value should implement the context.Context interface")
 	}
 
 	val = value.Type().Out(1)
-	errorInterface  := reflect.TypeOf((*error)(nil)).Elem()
+	errorInterface := reflect.TypeOf((*error)(nil)).Elem()
 	if !val.Implements(errorInterface) {
 		return errors.New("the returned value should implement the Error interface")
 	}
@@ -32,9 +33,9 @@ func validateStepFunc(f interface{}) error {
 	}
 
 	val = value.Type().In(0)
-	n := val.ConvertibleTo(reflect.TypeOf((*context.Context)(nil)).Elem())
+	n = val.ConvertibleTo(reflect.TypeOf((*context.Context)(nil)).Elem())
 	if !n {
-		return errors.New("the returned value should implement the Error interface")
+		return errors.New("the function should have Context as the first argument")
 	}
 	return nil
 }


Test patch:
diff --git a/context/get_test.go b/context/get_test.go
new file mode 100644
index 0000000..2bf9a54
--- /dev/null
+++ b/context/get_test.go
@@ -0,0 +1,356 @@
+// Code generated .* DO NOT EDIT.	
+package context
+
+import "testing"
+
+func TestContext_GetString(t *testing.T) {
+	ctx := New()
+	expected := string("example text")
+	ctx.Set("test", expected)
+	received := ctx.GetString("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetString_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := string("example text")
+	received := ctx.GetString("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetString_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetString should panic")
+        }
+    }()
+	_ = ctx.GetString("test", "example text", "example text")
+}
+
+func TestContext_GetString_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetString should panic")
+        }
+    }()
+	_ = ctx.GetString("test")
+}
+
+func TestContext_GetInt(t *testing.T) {
+	ctx := New()
+	expected := int(123)
+	ctx.Set("test", expected)
+	received := ctx.GetInt("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetInt_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := int(123)
+	received := ctx.GetInt("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetInt_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt should panic")
+        }
+    }()
+	_ = ctx.GetInt("test", 123, 123)
+}
+
+func TestContext_GetInt_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt should panic")
+        }
+    }()
+	_ = ctx.GetInt("test")
+}
+
+func TestContext_GetInt8(t *testing.T) {
+	ctx := New()
+	expected := int8(123)
+	ctx.Set("test", expected)
+	received := ctx.GetInt8("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetInt8_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := int8(123)
+	received := ctx.GetInt8("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetInt8_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt8 should panic")
+        }
+    }()
+	_ = ctx.GetInt8("test", 123, 123)
+}
+
+func TestContext_GetInt8_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt8 should panic")
+        }
+    }()
+	_ = ctx.GetInt8("test")
+}
+
+func TestContext_GetInt16(t *testing.T) {
+	ctx := New()
+	expected := int16(123)
+	ctx.Set("test", expected)
+	received := ctx.GetInt16("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetInt16_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := int16(123)
+	received := ctx.GetInt16("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetInt16_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt16 should panic")
+        }
+    }()
+	_ = ctx.GetInt16("test", 123, 123)
+}
+
+func TestContext_GetInt16_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt16 should panic")
+        }
+    }()
+	_ = ctx.GetInt16("test")
+}
+
+func TestContext_GetInt32(t *testing.T) {
+	ctx := New()
+	expected := int32(123)
+	ctx.Set("test", expected)
+	received := ctx.GetInt32("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetInt32_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := int32(123)
+	received := ctx.GetInt32("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetInt32_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt32 should panic")
+        }
+    }()
+	_ = ctx.GetInt32("test", 123, 123)
+}
+
+func TestContext_GetInt32_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt32 should panic")
+        }
+    }()
+	_ = ctx.GetInt32("test")
+}
+
+func TestContext_GetInt64(t *testing.T) {
+	ctx := New()
+	expected := int64(123)
+	ctx.Set("test", expected)
+	received := ctx.GetInt64("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetInt64_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := int64(123)
+	received := ctx.GetInt64("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetInt64_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt64 should panic")
+        }
+    }()
+	_ = ctx.GetInt64("test", 123, 123)
+}
+
+func TestContext_GetInt64_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetInt64 should panic")
+        }
+    }()
+	_ = ctx.GetInt64("test")
+}
+
+func TestContext_GetFloat32(t *testing.T) {
+	ctx := New()
+	expected := float32(123.5)
+	ctx.Set("test", expected)
+	received := ctx.GetFloat32("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetFloat32_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := float32(123.5)
+	received := ctx.GetFloat32("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetFloat32_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetFloat32 should panic")
+        }
+    }()
+	_ = ctx.GetFloat32("test", 123.5, 123.5)
+}
+
+func TestContext_GetFloat32_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetFloat32 should panic")
+        }
+    }()
+	_ = ctx.GetFloat32("test")
+}
+
+func TestContext_GetFloat64(t *testing.T) {
+	ctx := New()
+	expected := float64(123.5)
+	ctx.Set("test", expected)
+	received := ctx.GetFloat64("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetFloat64_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := float64(123.5)
+	received := ctx.GetFloat64("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetFloat64_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetFloat64 should panic")
+        }
+    }()
+	_ = ctx.GetFloat64("test", 123.5, 123.5)
+}
+
+func TestContext_GetFloat64_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetFloat64 should panic")
+        }
+    }()
+	_ = ctx.GetFloat64("test")
+}
+
+func TestContext_GetBool(t *testing.T) {
+	ctx := New()
+	expected := bool(false)
+	ctx.Set("test", expected)
+	received := ctx.GetBool("test")
+	if received != expected {
+		t.Errorf("expected %+v but received %+v", expected, received)
+	}
+}
+
+func TestContext_GetBool_WithDefaultValue(t *testing.T) {
+	ctx := New()
+	defaultValue := bool(false)
+	received := ctx.GetBool("test", defaultValue)
+	if received != defaultValue {
+		t.Errorf("expected %+v but received %+v", defaultValue, received)
+	}
+}
+
+func TestContext_GetBool_PanicOnMoreThanOneDefaultValue(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetBool should panic")
+        }
+    }()
+	_ = ctx.GetBool("test", false, false)
+}
+
+func TestContext_GetBool_PanicOnNotFound(t *testing.T) {
+	ctx := New()
+	defer func() {
+        if r := recover(); r == nil {
+            t.Error("the GetBool should panic")
+        }
+    }()
+	_ = ctx.GetBool("test")
+}
+	
diff --git a/gobdd_test.go b/gobdd_test.go
index b57665e..aa4aadb 100644
--- a/gobdd_test.go
+++ b/gobdd_test.go
@@ -1,11 +1,11 @@
 package gobdd
 
 import (
-	"context"
 	"errors"
 	"testing"
 
 	"github.com/go-bdd/assert"
+	"github.com/go-bdd/gobdd/context"
 )
 
 func TestScenarios(t *testing.T) {
@@ -85,18 +85,18 @@ func TestInvalidFunctionSignature(t *testing.T) {
 
 func addf(ctx context.Context, var1, var2 float32) (context.Context, error) {
 	res := var1 + var2
-	ctx = context.WithValue(ctx, "sumRes", res)
+	ctx.Set("sumRes", res)
 	return ctx, nil
 }
 
 func add(ctx context.Context, var1, var2 int) (context.Context, error) {
 	res := var1 + var2
-	ctx = context.WithValue(ctx, "sumRes", res)
+	ctx.Set("sumRes", res)
 	return ctx, nil
 }
 
 func checkf(ctx context.Context, sum float32) (context.Context, error) {
-	received := ctx.Value("sumRes")
+	received := ctx.Get("sumRes")
 
 	if sum != received {
 		return ctx, errors.New("the math does not work for you")
@@ -106,7 +106,7 @@ func checkf(ctx context.Context, sum float32) (context.Context, error) {
 }
 
 func check(ctx context.Context, sum int) (context.Context, error) {
-	received := ctx.Value("sumRes")
+	received := ctx.Get("sumRes")
 
 	if sum != received {
 		return ctx, errors.New("the math does not work for you")
diff --git a/steps_test.go b/steps_test.go
index e6571b5..cdfb404 100644
--- a/steps_test.go
+++ b/steps_test.go
@@ -1,20 +1,21 @@
 package gobdd
 
 import (
-	"context"
 	"testing"
+
+	"github.com/go-bdd/gobdd/context"
 )
 
 func TestValidateStepFunc(t *testing.T) {
-	testCases:= map[string]interface{}{
-		"function without arguments": func()(context.Context, error){ return nil, nil},
-		"function with invalid first arguments": func(int)(context.Context, error){ return nil, nil},
-		"function without returned values": func(context.Context){},
-		"function with invalid first returned value": func(context.Context)(int, error){ return 0, nil},
-		"function with invalid second returned value": func(context.Context)(context.Context, int){ return nil, 0},
+	testCases := map[string]interface{}{
+		"function without arguments":                  func() (context.Context, error) { return context.Context{}, nil },
+		"function with invalid first arguments":       func(int) (context.Context, error) { return context.Context{}, nil },
+		"function without returned values":            func(context.Context) {},
+		"function with invalid first returned value":  func(context.Context) (int, error) { return 0, nil },
+		"function with invalid second returned value": func(context.Context) (context.Context, int) { return context.Context{}, 0 },
 	}
 
-	for name, testCase := range testCases{
+	for name, testCase := range testCases {
 		t.Run(name, func(t *testing.T) {
 			if err := validateStepFunc(testCase); err == nil {
 				t.Errorf("the test should fail for the function")
@@ -24,7 +25,7 @@ func TestValidateStepFunc(t *testing.T) {
 }
 
 func TestValidateStepFunc_ValidFunction(t *testing.T) {
-	if err := validateStepFunc(func(context.Context) (context.Context, error) { return nil, nil}); err != nil {
-		t.Errorf("the test should NOT fail for the function: %s",err)
+	if err := validateStepFunc(func(context.Context) (context.Context, error) { return context.Context{}, nil }); err != nil {
+		t.Errorf("the test should NOT fail for the function: %s", err)
 	}
-}
\ No newline at end of file
+}
diff --git a/testhttp/setup.go b/testhttp/setup.go
index 0d05905..f30dccb 100644
--- a/testhttp/setup.go
+++ b/testhttp/setup.go
@@ -2,7 +2,6 @@ package testhttp
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -10,6 +9,7 @@ import (
 	"net/http"
 
 	"github.com/go-bdd/assert"
+	"github.com/go-bdd/gobdd/context"
 )
 
 type addStepper interface {
@@ -45,31 +45,31 @@ func Build(addStep addStepper, h httpHandler) TestHTTP {
 }
 
 func (t testHTTPMethods) iSetRequestBodyTo(ctx context.Context, body string) (context.Context, error) {
-	r := ctx.Value(RequestKey{}).(*http.Request)
+	r := ctx.Get(RequestKey{}).(*http.Request)
 	r.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(body)))
-	ctx = context.WithValue(ctx, RequestKey{}, r)
+	ctx.Set(RequestKey{}, r)
 	return ctx, nil
 }
 
 func (t testHTTPMethods) iSetRequestSetTo(ctx context.Context, headerName, value string) (context.Context, error) {
-	req := ctx.Value(RequestKey{}).(*http.Request)
+	req := ctx.Get(RequestKey{}).(*http.Request)
 	req.Header.Add(headerName, value)
 
-	ctx = context.WithValue(ctx, RequestKey{}, req)
+	ctx.Set(RequestKey{}, req)
 	return ctx, nil
 }
 
 func (t testHTTPMethods) responseHeaderEquals(ctx context.Context, headerName, expected string) (context.Context, error) {
-	resp := ctx.Value(ResponseKey{}).(Response)
+	resp := ctx.Get(ResponseKey{}).(Response)
 	given := resp.Header.Get(headerName)
 
 	return ctx, assert.Equals(expected, given)
 }
 
 func (t testHTTPMethods) theRequestHasBody(ctx context.Context, body string) (context.Context, error) {
-	req := ctx.Value(RequestKey{}).(*http.Request)
+	req := ctx.Get(RequestKey{}).(*http.Request)
 	req.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(body)))
-	ctx = context.WithValue(ctx, RequestKey{}, req)
+	ctx.Set(RequestKey{}, req)
 	return ctx, nil
 }
 
@@ -79,15 +79,15 @@ func (t testHTTPMethods) iHaveARequest(ctx context.Context, method, url string)
 		return ctx, err
 	}
 
-	ctx = context.WithValue(ctx, RequestKey{}, req)
+	ctx.Set(RequestKey{}, req)
 	return ctx, nil
 }
 
 func (t testHTTPMethods) theResponseIs(ctx context.Context, expectedResponse string) (context.Context, error) {
-	resp := ctx.Value(ResponseKey{}).(Response)
+	resp := ctx.Get(ResponseKey{}).(Response)
 	body, err := ioutil.ReadAll(resp.Body)
 	resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
-	ctx = context.WithValue(ctx, ResponseKey{}, resp)
+	ctx.Set(ResponseKey{}, resp)
 	if err != nil {
 		return ctx, fmt.Errorf("an error while reading the body: %s", err)
 	}
@@ -99,10 +99,10 @@ func (t testHTTPMethods) theResponseIs(ctx context.Context, expectedResponse str
 }
 
 func (t testHTTPMethods) validJSON(ctx context.Context) (context.Context, error) {
-	resp := ctx.Value(ResponseKey{}).(Response)
+	resp := ctx.Get(ResponseKey{}).(Response)
 	body, err := ioutil.ReadAll(resp.Body)
 	resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
-	ctx = context.WithValue(ctx, ResponseKey{}, resp)
+	ctx.Set(ResponseKey{}, resp)
 	if err != nil {
 		return ctx, fmt.Errorf("an error while reading the body: %s", err)
 	}
@@ -114,13 +114,13 @@ func (t testHTTPMethods) validJSON(ctx context.Context) (context.Context, error)
 }
 
 func (t testHTTPMethods) iMakeRequest(ctx context.Context) (context.Context, error) {
-	req := ctx.Value(RequestKey{}).(*http.Request)
+	req := ctx.Get(RequestKey{}).(*http.Request)
 	resp, err := t.tHTTP.MakeRequest(req)
 	if err != nil {
 		return ctx, err
 	}
 
-	ctx = context.WithValue(ctx, ResponseKey{}, resp)
+	ctx.Set(ResponseKey{}, resp)
 	return ctx, nil
 }
 
@@ -135,12 +135,12 @@ func (t testHTTPMethods) makeRequest(ctx context.Context, method, url string) (c
 		return ctx, err
 	}
 
-	ctx = context.WithValue(ctx, ResponseKey{}, resp)
+	ctx.Set(ResponseKey{}, resp)
 	return ctx, nil
 }
 
 func (t testHTTPMethods) statusCodeEquals(ctx context.Context, expectedStatus int) (context.Context, error) {
-	resp := ctx.Value(ResponseKey{}).(Response)
+	resp := ctx.Get(ResponseKey{}).(Response)
 
 	if expectedStatus != resp.Code {
 		return ctx, fmt.Errorf("expected status code: %d but %d given", expectedStatus, resp.Code)
diff --git a/testhttp_test.go b/testhttp_test.go
index 16f2401..337d91a 100644
--- a/testhttp_test.go
+++ b/testhttp_test.go
@@ -1,12 +1,11 @@
 package gobdd
 
 import (
-	"context"
 	"net/http"
 	"testing"
 
 	"github.com/go-bdd/assert"
-
+	"github.com/go-bdd/gobdd/context"
 	"github.com/go-bdd/gobdd/testhttp"
 )
 
@@ -42,29 +41,29 @@ func TestHTTP(t *testing.T) {
 }
 
 func requestHasMethodSetTo(ctx context.Context, method string) (context.Context, error) {
-	r := ctx.Value(testhttp.RequestKey{}).(*http.Request)
+	r := ctx.Get(testhttp.RequestKey{}).(*http.Request)
 	return ctx, assert.Equals(method, r.Method)
 }
 
 func requestBodyIsNil(ctx context.Context) (context.Context, error) {
-	r := ctx.Value(testhttp.RequestKey{}).(*http.Request)
+	r := ctx.Get(testhttp.RequestKey{}).(*http.Request)
 	return ctx, assert.Nil(r.Body)
 }
 
 func urlIsSetTo(ctx context.Context, url string) (context.Context, error) {
-	r := ctx.Value(testhttp.RequestKey{}).(*http.Request)
+	r := ctx.Get(testhttp.RequestKey{}).(*http.Request)
 	return ctx, assert.Equals(url, r.URL.String())
 }
 
 func ISetHeaderTo(ctx context.Context, headerName, value string) (context.Context, error) {
-	r := ctx.Value(testhttp.RequestKey{}).(*http.Request)
+	r := ctx.Get(testhttp.RequestKey{}).(*http.Request)
 	r.Header.Set(headerName, value)
-	ctx = context.WithValue(ctx, testhttp.RequestKey{}, r)
+	ctx.Set(testhttp.RequestKey{}, r)
 	return ctx, nil
 }
 
 func requestHasHeaderSetTo(ctx context.Context, headerName, expected string) (context.Context, error) {
-	r := ctx.Value(testhttp.RequestKey{}).(*http.Request)
+	r := ctx.Get(testhttp.RequestKey{}).(*http.Request)
 	given := r.Header.Get(headerName)
 	return ctx, assert.Equals(expected, given)
 }