-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added fixtures #8
Changes from all commits
0713ad7
02ae022
472fa92
b9171c7
383cd6e
d716739
d27fb32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ sudo: false | |
|
||
go: | ||
- 1.8 | ||
- 1.9 | ||
- "1.10" | ||
- tip | ||
|
||
before_install: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,3 +95,109 @@ func (as *ActionSuite) Test_TodosResource_Update() { | |
as.Equal("Learn Go", todo.Title) | ||
} | ||
``` | ||
|
||
## Fixtures (Test Data) | ||
|
||
Often it is useful to load a series of data into the database at the start of the test to make testing. For example, you need to have a user in the database to log a person into the application, or you need some data in the database to test destroying that data. Fixtures let us solve these problems easily. | ||
|
||
### Usage | ||
|
||
First you need to setup your test suite to use fixtures. You can do this by using `suite.NewActionWithFixtures` or `suite.NewModelWithFixtures` methods to create new test suites that take a `packr.Box` pointing to where the files for this suite live. | ||
|
||
```go | ||
package actions | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gobuffalo/packr" | ||
"github.com/gobuffalo/suite" | ||
) | ||
|
||
type ActionSuite struct { | ||
*suite.Action | ||
} | ||
|
||
func Test_ActionSuite(t *testing.T) { | ||
action, err := suite.NewActionWithFixtures(App(), packr.NewBox("../fixtures")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
as := &ActionSuite{ | ||
Action: action, | ||
} | ||
suite.Run(t, as) | ||
} | ||
``` | ||
|
||
Once your suite is set up, you can create `N` numbers of `*.toml` files in the directory you've chosen for your fixtures, in this example, `../fixtures`. | ||
|
||
### Example Fixture File | ||
|
||
```toml | ||
[[scenario]] | ||
name = "lots of widgets" | ||
|
||
[[scenario.table]] | ||
name = "widgets" | ||
|
||
[[scenario.table.row]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great if we could do something like loops for rows There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's plush, so you can! :) |
||
id = "<%= uuidNamed("widget") %>" | ||
name = "This is widget #1" | ||
body = "some widget body" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
|
||
[[scenario.table.row]] | ||
id = "<%= uuid() %>" | ||
name = "This is widget #2" | ||
body = "some widget body" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
|
||
[[scenario.table]] | ||
name = "users" | ||
|
||
[[scenario.table.row]] | ||
id = "<%= uuid() %>" | ||
name = "Mark Bates" | ||
admin = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to add a fake data generation helper, probably we could use something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed! that would be great. PR please! ;) |
||
price = 19.99 | ||
widget_id = "<%= uuidNamed("widget") %>" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
``` | ||
|
||
#### Helper Methods | ||
|
||
The `*.toml` files all get run through [https://github.com/gobuffalo/plush](https://github.com/gobuffalo/plush) before they're decoded, so you can make use of the helpful helper methods that ship with Plush. | ||
|
||
We've also add a couple of useful helpers for you as well: | ||
|
||
* `uuid()` - returns a new `github.com/gobuffalo/uuid.UUID` | ||
* `now()` - returns `time.Now()` | ||
* `uuidNamed(name)` - will attempt to return a previously declared UUID with that name, useful, for relations/associations. If there was one that wasn't defined with that name, a new one will be created. | ||
|
||
### Using in Tests | ||
|
||
In your suite tests you need to call the `LoadFixture` method giving it the name of the fixtures you would like to use for this test. | ||
|
||
```go | ||
func (as *ActionSuite) Test_WidgetsResource_List() { | ||
as.LoadFixture("lots of widgets") | ||
res := as.HTML("/widgets").Get() | ||
|
||
body := res.Body.String() | ||
as.Contains(body, "widget #1") | ||
as.Contains(body, "widget #2") | ||
} | ||
``` | ||
|
||
### FAQs | ||
|
||
* _Can I call `LoadFixture` more than once in a test?_ - Absolutely! Call it as many times as you want! | ||
* _Can I load multiple rows into a table in one scenario?_ - Absolutely! | ||
* _Can I load data into multiple tables in one scenario?_ - Absolutely! | ||
* _Will it load all my fixtures?_ - No, you have to load specific scenarios, so don't be afraid to create lots of scenarios and only call the ones you need per test. | ||
* _Will this polute my database, and how do I clear data between tests?_ - No need to worry, the suite will truncate any data in your database between test runs, so you never have to worry about it. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package fix | ||
|
||
import ( | ||
"path/filepath" | ||
"sync" | ||
|
||
"github.com/BurntSushi/toml" | ||
"github.com/gobuffalo/packr" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var scenes = map[string]Scenario{} | ||
var moot = &sync.RWMutex{} | ||
|
||
func Init(box packr.Box) error { | ||
err := box.Walk(func(path string, file packr.File) error { | ||
if filepath.Ext(path) != ".toml" { | ||
return nil | ||
} | ||
|
||
x, err := render(file) | ||
|
||
sc := Scenarios{} | ||
_, err = toml.Decode(x, &sc) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
|
||
moot.Lock() | ||
for _, s := range sc.Scenarios { | ||
scenes[s.Name] = s | ||
} | ||
moot.Unlock() | ||
return nil | ||
}) | ||
return err | ||
} | ||
|
||
func Find(name string) (Scenario, error) { | ||
moot.RLock() | ||
s, ok := scenes[name] | ||
moot.RUnlock() | ||
if !ok { | ||
return Scenario{}, errors.Errorf("could not find a scenario named %q", name) | ||
} | ||
return s, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package fix | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gobuffalo/packr" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_Init_And_Find(t *testing.T) { | ||
r := require.New(t) | ||
|
||
box := packr.NewBox("./init-fixtures") | ||
|
||
r.NoError(Init(box)) | ||
|
||
s, err := Find("lots of widgets") | ||
r.NoError(err) | ||
r.Equal("lots of widgets", s.Name) | ||
|
||
r.Len(s.Tables, 2) | ||
|
||
table := s.Tables[0] | ||
r.Equal("widgets", table.Name) | ||
r.Len(table.Row, 2) | ||
|
||
row := table.Row[0] | ||
r.NotZero(row["id"]) | ||
r.NotZero(row["created_at"]) | ||
r.NotZero(row["updated_at"]) | ||
r.Equal("This is widget #1", row["name"]) | ||
r.Equal("some widget body", row["body"]) | ||
|
||
wid := row["id"] | ||
|
||
row = table.Row[1] | ||
r.NotZero(row["id"]) | ||
r.NotZero(row["created_at"]) | ||
r.NotZero(row["updated_at"]) | ||
r.Equal("This is widget #2", row["name"]) | ||
r.Equal("some widget body", row["body"]) | ||
|
||
table = s.Tables[1] | ||
r.Equal("users", table.Name) | ||
r.Len(table.Row, 1) | ||
|
||
row = table.Row[0] | ||
r.NotZero(row["id"]) | ||
r.NotZero(row["created_at"]) | ||
r.NotZero(row["updated_at"]) | ||
r.True(row["admin"].(bool)) | ||
r.Equal(19.99, row["price"].(float64)) | ||
r.Equal(wid, row["widget_id"]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[[scenario]] | ||
name = "lots of widgets" | ||
|
||
[[scenario.table]] | ||
name = "widgets" | ||
|
||
[[scenario.table.row]] | ||
id = "<%= uuidNamed("widget") %>" | ||
name = "This is widget #1" | ||
body = "some widget body" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
|
||
[[scenario.table.row]] | ||
id = "<%= uuid() %>" | ||
name = "This is widget #2" | ||
body = "some widget body" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
|
||
[[scenario.table]] | ||
name = "users" | ||
|
||
[[scenario.table.row]] | ||
id = "<%= uuid() %>" | ||
name = "Mark Bates" | ||
admin = true | ||
price = 19.99 | ||
widget_id = "<%= uuidNamed("widget") %>" | ||
created_at = "<%= now() %>" | ||
updated_at = "<%= now() %>" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package fix | ||
|
||
import ( | ||
"io/ioutil" | ||
"time" | ||
|
||
"github.com/gobuffalo/packr" | ||
"github.com/gobuffalo/plush" | ||
"github.com/gobuffalo/uuid" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func render(file packr.File) (string, error) { | ||
b, err := ioutil.ReadAll(file) | ||
if err != nil { | ||
return "", errors.WithStack(err) | ||
} | ||
|
||
return plush.Render(string(b), plush.NewContextWith(map[string]interface{}{ | ||
"uuid": func() uuid.UUID { | ||
u, _ := uuid.NewV4() | ||
return u | ||
}, | ||
"uuidNamed": uuidNamed, | ||
"now": time.Now, | ||
})) | ||
|
||
} | ||
|
||
func uuidNamed(name string, help plush.HelperContext) uuid.UUID { | ||
u, _ := uuid.NewV4() | ||
if ux, ok := help.Value(name).(uuid.UUID); ok { | ||
return ux | ||
} | ||
help.Set(name, u) | ||
return u | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package fix | ||
|
||
type Scenario struct { | ||
Name string `toml:"name"` | ||
Tables Tables `toml:"table"` | ||
} | ||
|
||
// type Scenarios []Scenario | ||
type Scenarios struct { | ||
Scenarios []Scenario `toml:"scenario"` | ||
} | ||
|
||
type Table struct { | ||
Name string `toml:"name"` | ||
Row []map[string]interface{} `toml:"row"` | ||
} | ||
|
||
type Tables []Table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought here: since fixtures are intended to be used in a test context, why do we have to use packr here instead of a regular fs? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question! I almost did that, then I went with packr instead. The reason why is when
go test
runs it does all sorts of stuff withcd
and such under the covers. The problem with this is that if you use relative files, the relativity can change. Part of packr's design is based on turning relative into absolute. So using packr means we don't have to worry about those issues.Another advantage of packr is it opens things up to interesting, programmatic things you can do during your tests, like import someone else box.
TL;DR: relativity can change when running tests; packr doesn't have this problem so it's "safer". :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍