Skip to content

Commit

Permalink
Add mocha report and update README.md file
Browse files Browse the repository at this point in the history
  • Loading branch information
arsham committed Jul 16, 2019
1 parent fd00013 commit ac99dc5
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 41 deletions.
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: go
dist: xenial

os:
- linux

env:
- GO111MODULE=on

go:
- 1.12.x

script:
- go test -failfast -v -coverprofile=coverage.txt -covermode=atomic ./...

after_success:
- bash <(curl -s https://codecov.io/bash)
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.PHONY: test
test:
@echo "running tests on $(run). waiting for changes..."
@-zsh -c "go test ./...; repeat 100 printf '#'; echo"
@reflex -d none -r "(\.go$$)|(go.mod)" -- zsh -c "go test ./...; repeat 100 printf '#'"

.PHONY: test_race
test_race:
@echo "running tests on $(run). waiting for changes..."
@-zsh -c "go test -race ./...; repeat 100 printf '#'; echo"
@reflex -d none -r "(\.go$$)|(go.mod)" -- zsh -c "go test -race ./...; repeat 100 printf '#'"

.PHONY: third-party
third-party:
@go get -u github.com/cespare/reflex

.PHONY: clean
clean:
go clean -cache -testcache
152 changes: 112 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,151 @@
# dbtesting

Utility for using with go-sqlmock library.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![GoDoc](https://godoc.org/github.com/arsham/dbtesting?status.svg)](http://godoc.org/github.com/arsham/dbtesting)
[![Build Status](https://travis-ci.org/arsham/dbtesting.svg?branch=master)](https://travis-ci.org/arsham/dbtesting)
[![Coverage Status](https://codecov.io/gh/arsham/dbtesting/branch/master/graph/badge.svg)](https://codecov.io/gh/arsham/dbtesting)

This library can be used in the [go-sqlmock][go-sqlmock] test cases for cases
that values are random but it is important to check the values passed in
This library has a few helpers for using in tests.

1. [Spec Reports](#spec-reports)
* [Usage](#usage)
2. [SQLMock Helpers](#sqlmock-helpers)
* [ValueRecorder](#valuerecorder)
* [OkValue](#okvalue)
3. [Testing](#testing)
4. [License](#license)

## Spec Reports

`Mocha` is a reporter for printing Mocha inspired reports when using
[spec BDD library][spec].

### Usage

```go
import "github.com/arsham/dbtesting"

func TestFoo(t *testing.T) {
spec.Run(t, "Foo", func(t *testing.T, when spec.G, it spec.S) {
// ...
}, spec.Report(&dbtesting.Mocha{}))
}

```

You can set an `io.Writer` to `Mocha.Out` to redirect the output, otherwise it
prints to the `os.Stdout`.

## SQLMock Helpers

There a couple of helpers for using with [go-sqlmock][go-sqlmock] test cases for
cases that values are random but it is important to check the values passed in
queries.

## ValueRecorder
### ValueRecorder

If you generate a UUID and use it in multiple queries and you want to make sure
the queries are passed with correct IDs. For instance if in your code you have:
If you have an value and use it in multiple queries, and you want to
make sure the queries are passed with correct values, you can use the
`ValueRecorder`. For example UUIDs, time and random values.

For instance if the first query generates a random number but it is essential to
use the same value on next queries:

```go
import "database/sql"

// ...

// assume num has been generated randomly
num := 666
_, err := tx.ExecContext(ctx, "INSERT INTO life (value) VALUE ($1)", num)
// error check
_, err := tx.ExecContext(ctx, "INSERT INTO reality (value) VALUE ($1)", num)
// error check
_, err := tx.ExecContext(ctx, "INSERT INTO everywhere (value) VALUE ($1)", num)
// error check
func TestFoo(t *testing.T) {
// ...
// assume num has been generated randomly
num := 666
_, err := tx.ExecContext(ctx, "INSERT INTO life (value) VALUE ($1)", num)
// error check
_, err = tx.ExecContext(ctx, "INSERT INTO reality (value) VALUE ($1)", num)
// error check
_, err = tx.ExecContext(ctx, "INSERT INTO everywhere (value) VALUE ($1)", num)
// error check
}
```

Your tests can be checked easily like this:
```go
import (
"github.com/arsham/dbtesting"
"github.com/DATA-DOG/go-sqlmock"
// ...
)

rec := dbtesting.NewValueRecorder()
mock.ExpectExec("INSERT INTO life .+").
WithArgs(rec.Record("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO reality .+").
WithArgs(rec.For("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO everywhere .+").
WithArgs(rec.For("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
func TestFoo(t *testing.T) {
// ...
rec := dbtesting.NewValueRecorder()
mock.ExpectExec("INSERT INTO life .+").
WithArgs(rec.Record("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO reality .+").
WithArgs(rec.For("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO everywhere .+").
WithArgs(rec.For("truth")).
WillReturnResult(sqlmock.NewResult(1, 1))
}
```

Recorded values can be retrieved by casting to their types:
```go
rec.Value("true").(string)
```

## OkValue
There are two rules for using the `ValueRecorder`:
1. You can only record for a value once.
2. You should record a value before you call `For` or `Value`.

It will panic if these requirements are not met.

When you are only interested in checking some arguments passed to the Exec/Query
### OkValue

If you are only interested in checking some arguments passed to the Exec/Query
functions and you don't want to check everything (maybe because thy are not
relevant to the current test), you can use the `OkValue`.
relevant to the current test), you can use `OkValue`.

```go
import (
"github.com/arsham/dbtesting"
"github.com/DATA-DOG/go-sqlmock"
// ...
)

ok := dbtesting.OkValue
mock.ExpectExec("INSERT INTO life .+").
WithArgs(
dbtesting.OkValue,
dbtesting.OkValue,
dbtesting.OkValue,
"import value"
dbtesting.OkValue,
dbtesting.OkValue,
dbtesting.OkValue,
ok,
ok,
ok,
"important value"
ok,
ok,
ok,
)
```

## LICENSE
## Testing

To run the tests:

```bash
make
```
or for with `-race` flag:
```bash
make test_race
```

If you don't have `reflex` installed, run the following once:
```bash
make third-party
```

## License

Use of this source code is governed by the Apache 2.0 license. License can be
found in the [LICENSE](./LICENSE) file.

[go-sqlmock]: github.com/DATA-DOG/go-sqlmock
[go-sqlmock]: https://github.com/DATA-DOG/go-sqlmock
[spec]: https://github.com/sclevine/spec
27 changes: 27 additions & 0 deletions dbtesting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,30 @@ func ExampleValueRecorder() {
// Output:
// got recorded value: 666
}

func ExampleValueRecorder_value() {
db, mock, err := sqlmock.New()
if err != nil {
panic(err)
}
defer db.Close()
defer func() {
if err := mock.ExpectationsWereMet(); err != nil {
fmt.Printf("there were unfulfilled expectations: %s", err)
}
}()
rec := dbtesting.NewValueRecorder()
mock.ExpectExec("INSERT INTO life .+").
WithArgs(rec.Record("meaning")).
WillReturnResult(sqlmock.NewResult(1, 1))

_, err = db.Exec("INSERT INTO life (name) VALUE ($1)", 42)
if err != nil {
panic(err)
}

fmt.Printf("Meaning of life: %d", rec.Value("meaning").(int64))

// Output:
// Meaning of life: 42
}
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ module github.com/arsham/dbtesting

go 1.12

require github.com/DATA-DOG/go-sqlmock v1.3.3
require (
github.com/DATA-DOG/go-sqlmock v1.3.3
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/sclevine/spec v1.2.0
github.com/stretchr/testify v1.3.0
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
64 changes: 64 additions & 0 deletions mocha.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dbtesting

import (
"fmt"
"io"
"os"
"strings"
"sync"
"testing"

"github.com/sclevine/spec"
)

// Mocha prints spec reports in terminal.
type Mocha struct {
once sync.Once
Out io.Writer // if not set it will print to stdout
}

func (m *Mocha) setup() {
if m.Out == nil {
m.Out = os.Stdout
}
}

// Start prints some information when the suite is started.
func (m *Mocha) Start(_ *testing.T, plan spec.Plan) {
m.once.Do(m.setup)
fmt.Fprintln(m.Out, "Suite:", plan.Text)
fmt.Fprintf(m.Out, "Total: %d | Focused: %d | Pending: %d\n", plan.Total, plan.Focused, plan.Pending)
if plan.HasRandom {
fmt.Fprintln(m.Out, "Random seed:", plan.Seed)
}
if plan.HasFocus {
fmt.Fprintln(m.Out, "Focus is active.")
}
}

// Specs prints information about specs' results while suite is running.
func (m *Mocha) Specs(_ *testing.T, specs <-chan spec.Spec) {
m.once.Do(m.setup)
var passed, failed, skipped int
fs := "\033[31m" + "✘"
ps := "\033[32m" + "✔"
ss := "\033[32m" + "✱"
for s := range specs {
switch {
case s.Failed:
failed++
fmt.Fprint(m.Out, fs)
case s.Skipped:
skipped++
fmt.Fprint(m.Out, ss)
default:
passed++
fmt.Fprint(m.Out, ps)
}
for i, txt := range s.Text {
fmt.Fprintln(m.Out, strings.Repeat(" ", i*3), " ", txt)
}
fmt.Fprint(m.Out, "\033[0m")
}
fmt.Fprintf(m.Out, "\nPassed: %d | Failed: %d | Skipped: %d\n\n", passed, failed, skipped)
}
Loading

0 comments on commit ac99dc5

Please sign in to comment.