Skip to content

Commit

Permalink
Simplfy API (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
qrdl committed Mar 9, 2024
1 parent 6fab13b commit b34af62
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 392 deletions.
15 changes: 7 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ OS/arch combinations:

It is recommended to switch off compiler optimisations and disable function inlining using `-gcflags="all=-N -l"` CLI option when running tests, like this:

`go test -gcflags="all=-N -l" [<path>]`
`go test -gcflags="all=-N -l" ./...`

Typical use:
```
import . "github.com/qrdl/testaroli"
// you want to test function foo() which in turn calls function bar(), so you
// override function bar() to check whether it is called with correct argument
// and to return predefined result
Expand All @@ -46,22 +48,19 @@ func bar(baz int) error {
}
func TestBarFailing(t *testing.T) {
mock := testaroli.New(context.TODO(), t)
// v-- how many runs expected
testaroli.Override(1, bar, func(a int) error {
testaroli.Expectation().CheckArgs(a) // <-- arg value checked here
Override(TestingContext(t), bar, Once, func(a int) error {
Expectation().CheckArgs(a) // <-- arg value checked here
return ErrInvalid
})(42) // <-- expected argument value
err := foo()
if !errors.Is(err, ErrInvalid) {
t.Errorf("unexpected %v", err)
}
it err = mock.ExpectationsWereMet(); err != nil {
it err = ExpectationsWereMet(); err != nil {
t.Error(err)
}
}
```

See more advanced usage examples in [examples](examples) directory.
See more advanced usage examples in [examples](../examples) directory.
132 changes: 132 additions & 0 deletions equal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package testaroli

import (
"fmt"
"reflect"
)

// standard reflect.Value.Equal has several issues:
// - it compares pointers only as addresses
// - it doesn't compare maps
// - it doesn't compare slices
// - it doesn't explain what exactly has failed
// - it panics
// so I've rolled my own, based on reflect's implementation
func equal(a, e reflect.Value) (bool, string) {
if a.Kind() == reflect.Interface {
a = a.Elem()
}
if e.Kind() == reflect.Interface {
e = e.Elem()
}

if !a.IsValid() || !e.IsValid() {
return a.IsValid() == e.IsValid(), "cannot compare invalid value with valid one"
}

if a.Kind() != e.Kind() || a.Type() != e.Type() {
return false, fmt.Sprintf("actual type '%s' differs from expected '%s'", a.Type(), e.Type())
}

switch a.Kind() {
case reflect.Bool:
return a.Bool() == e.Bool(), ""
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return a.Int() == e.Int(), ""
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return a.Uint() == e.Uint(), ""
case reflect.Float32, reflect.Float64:
return a.Float() == e.Float(), ""
case reflect.Complex64, reflect.Complex128:
return a.Complex() == e.Complex(), ""
case reflect.String:
return a.String() == e.String(), ""
case reflect.Chan:
return a.Pointer() == e.Pointer(), ""
case reflect.Pointer, reflect.UnsafePointer:
if a.Pointer() == e.Pointer() {
return true, ""
}
res, str := equal(reflect.Indirect(a), reflect.Indirect(e))
if !res && str == "" {
str = fmt.Sprintf("actual value '%v' differs from expected '%v'", reflect.Indirect(a), reflect.Indirect(e))
}
return res, str
case reflect.Array:
// u and v have the same type so they have the same length
vl := a.Len()
if vl == 0 {
return true, ""
}
for i := 0; i < vl; i++ {
res, str := equal(a.Index(i), e.Index(i))
if !res {
if str == "" {
str = fmt.Sprintf("actual value '%v' differs from expected '%v'",
a.Index(i), e.Index(i))
}
return false, fmt.Sprintf("array elem %d: %s", i, str)
}
}
return true, ""
case reflect.Struct:
// u and v have the same type so they have the same fields
nf := a.NumField()
for i := 0; i < nf; i++ {
res, str := equal(a.Field(i), e.Field(i))
if !res {
if str == "" {
str = fmt.Sprintf("actual value '%v' differs from expected '%v'",
a.Field(i), e.Field(i))
}
return false, fmt.Sprintf("struct field '%s': %s", a.Type().Field(i).Name, str)
}
}
return true, ""
case reflect.Map:
if a.Pointer() == e.Pointer() {
return true, ""
}
keys := a.MapKeys()
if len(keys) != len(e.MapKeys()) {
return false, "map lengths differ"
}
for _, k := range keys {
res, str := equal(a.MapIndex(k), e.MapIndex(k))
if !res {
if str == "" {
str = fmt.Sprintf("actual value '%v' differs from expected '%v'",
a.MapIndex(k), e.MapIndex(k))
}
return false, fmt.Sprintf("map value for key '%v': %s", k, str)
}
}
return true, ""
case reflect.Func:
return a.Pointer() == e.Pointer(), ""
// function can be equal only to itself
case reflect.Slice:
if a.Pointer() == e.Pointer() {
return true, ""
}
vl := a.Len()
if vl != e.Len() {
return false, "slice lengths differ"
}
if vl == 0 {
return true, ""
}
for i := 0; i < vl; i++ {
res, str := equal(a.Index(i), e.Index(i))
if !res {
if str == "" {
str = fmt.Sprintf("actual value '%v' differs from expected '%v'",
a.Index(i), e.Index(i))
}
return false, fmt.Sprintf("slice elem %d: %s", i, str)
}
}
return true, ""
}
return false, "invalid variable Kind" // should never happen
}
52 changes: 26 additions & 26 deletions examples/functions/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,73 @@ import (
"errors"
"testing"

"github.com/qrdl/testaroli"
. "github.com/qrdl/testaroli"
)

func TestSuccess(t *testing.T) {
mock := testaroli.New(context.TODO(), t)
ctx := TestingContext(t)

testaroli.Override(1, accStatus, func(acc string) AccStatus {
testaroli.Expectation().CheckArgs(acc)
Override(ctx, accStatus, Once, func(acc string) AccStatus {
Expectation().CheckArgs(acc)
return AccStatusDebitable
})("1024")

testaroli.Override(1, accStatus, func(acc string) AccStatus {
testaroli.Expectation().CheckArgs(acc)
Override(ctx, accStatus, Once, func(acc string) AccStatus {
Expectation().CheckArgs(acc)
return AccStatusCreditable
})("2048")

testaroli.Override(1, accBalance, func(acc string) float64 {
testaroli.Expectation().CheckArgs(acc)
Override(ctx, accBalance, Once, func(acc string) float64 {
Expectation().CheckArgs(acc)
return 1000
})("1024")

testaroli.Override(1, debit, func(acc string, amount float64) {
testaroli.Expectation().CheckArgs(acc, amount)
Override(ctx, debit, Once, func(acc string, amount float64) {
Expectation().CheckArgs(acc, amount)
})("1024", 200)

testaroli.Override(1, credit, func(acc string, amount float64) {
testaroli.Expectation().CheckArgs(acc, amount)
Override(ctx, credit, Once, func(acc string, amount float64) {
Expectation().CheckArgs(acc, amount)
})("2048", 200)

err := transfer("1024", "2048", 200)
testError(t, nil, err)
testError(t, nil, mock.ExpectationsWereMet())
testError(t, nil, ExpectationsWereMet())
}

func TestNoEnoughFunds(t *testing.T) {
mock := testaroli.New(context.TODO(), t)
func TestNotEnoughFunds(t *testing.T) {
ctx := TestingContext(t)

testaroli.Override(1, accStatus, func(acc string) AccStatus {
testaroli.Expectation().CheckArgs(acc)
Override(ctx, accStatus, Once, func(acc string) AccStatus {
Expectation().CheckArgs(acc)
return AccStatusDebitable
})("1024")

testaroli.Override(1, accStatus, func(acc string) AccStatus {
testaroli.Expectation().CheckArgs(acc)
Override(ctx, accStatus, Once, func(acc string) AccStatus {
Expectation().CheckArgs(acc)
return AccStatusCreditable
})("2048")

testaroli.Override(1, accBalance, func(acc string) float64 {
testaroli.Expectation().Expect("1024").CheckArgs(acc)
Override(ctx, accBalance, Once, func(acc string) float64 {
Expectation().Expect("1024").CheckArgs(acc)
return 100
})

err := transfer("1024", "2048", 200)
testError(t, ErrNotEnoughFunds, err)
testError(t, nil, mock.ExpectationsWereMet())
testError(t, nil, ExpectationsWereMet())
}

type contextKey int

const key = contextKey(1)

func TestNotCreditable(t *testing.T) {
mock := testaroli.New(context.WithValue(context.TODO(), key, "1024"), t)
defer func() { testError(t, nil, mock.ExpectationsWereMet()) }()
ctx := context.WithValue(TestingContext(t), key, "1024")
defer func() { testError(t, nil, ExpectationsWereMet()) }()

testaroli.Override(2, accStatus, func(acc string) AccStatus {
f := testaroli.Expectation()
Override(ctx, accStatus, 2, func(acc string) AccStatus {
f := Expectation()
if f.RunNumber() == 0 {
f.Expect(f.Context().Value(key).(string))
} else {
Expand Down
27 changes: 10 additions & 17 deletions examples/methods/methods_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package main

import (
"context"
"errors"
"testing"

"github.com/qrdl/testaroli"
. "github.com/qrdl/testaroli"
)

func TestTransferOK(t *testing.T) {
Expand All @@ -14,42 +13,36 @@ func TestTransferOK(t *testing.T) {
}

func TestTransferDebitAccountNotOK(t *testing.T) {
mock := testaroli.New(context.TODO(), t)

testaroli.Override(1, Acc.IsDebitable, func(Acc) bool {
testaroli.Expectation()
Override(TestingContext(t), Acc.IsDebitable, Once, func(Acc) bool {
Expectation()
return false
})

err := Transfer("1024", "2048", 2.0)
testError(t, ErrInvalid, err)
testError(t, nil, mock.ExpectationsWereMet())
testError(t, nil, ExpectationsWereMet())
}

func TestTransferNotEnoughFunds(t *testing.T) {
mock := testaroli.New(context.TODO(), t)

testaroli.Override(1, Acc.Balance, func(acc Acc) float64 {
testaroli.Expectation().CheckArgs(acc)
Override(TestingContext(t), Acc.Balance, Once, func(acc Acc) float64 {
Expectation().CheckArgs(acc)
return acc.balance * -1
})(Acc{status: AccStatusDebitable | AccStatusCreditable, balance: 123.45, number: "1024"})

err := Transfer("1024", "2048", 2.0)
testError(t, ErrNotEnoughFunds, err)
testError(t, nil, mock.ExpectationsWereMet())
testError(t, nil, ExpectationsWereMet())
}

func TestTransferFail(t *testing.T) {
mock := testaroli.New(context.TODO(), t)

testaroli.Override(1, interAccountTransfer, func(from, to *Acc, amount float64) error {
testaroli.Expectation().Expect("1024", "2048", 2.0).CheckArgs(from.number, to.number, amount)
Override(TestingContext(t), interAccountTransfer, Once, func(from, to *Acc, amount float64) error {
Expectation().Expect("1024", "2048", 2.0).CheckArgs(from.number, to.number, amount)
return ErrInvalid
})

err := Transfer("1024", "2048", 2.0)
testError(t, ErrInvalid, err)
testError(t, nil, mock.ExpectationsWereMet())
testError(t, nil, ExpectationsWereMet())
}

func testError(t *testing.T, expected, actual error) {
Expand Down
Loading

0 comments on commit b34af62

Please sign in to comment.