I am learning how to create Golang tests for an Appengine app.
The documentation examples don't make sense to me.
https://cloud.google.com/appengine/docs/standard/go/tools/localunittesting/reference
Documentation seems to say you can create a context := aetest.NewContext()
When I attempt to do so, I'm getting an error that aetest.NewContext requires arguments.
$ go test -v
./skincare_test.go:12: not enough arguments in call to aetest.NewContext
have ()
want (*aetest.Options)
./skincare_test.go:12: assignment count mismatch: 3 = 2
FAIL _/Users/Bryan/work/gocode/skincarereview [build failed]
content of skincare_test.go:
package skincare
import (
"net/http"
"net/http/httptest"
"testing"
"appengine/aetest"
)
func TestIndexHandler(t *testing.T) {
ctx, done, err := aetest.NewContext()
if err != nil {
t.Fatal(err)
}
defer done()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(root)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
expected := "<div>Name"
if rr.Body.String() != expected {
t.Errorf("handler returned expected body: got %v want %v",
rr.Body.String(), expected)
}
}
I learn best by looking at example code, where can I find examples of Tests for Go web applications that use Appengine datastore?
The examples in the documentation are so simple that I don't see how I'm supposed to do more complicated testing.
It says 2 things:
1) You are missing a required parameter *aetest.Options
2) that you can NOT assign result aetest.NewContext() that consist of 2 variable to a set of 3 variables.
Check what is the output of the function. I guess it is just (context.Context, error) - I suspect the done is moved to the *aetest.Options somehow.
Unfortunately my access to docs is blocked right now.
You are using the old version of the app engine package (appengine/aetest instead of google.golang.org/appengine/aetest). The newer version does not require arguments.
Related
I want to create a database driven application using Golang. I am trying to do it TDD way.
When I try to test methods that make Sql queries, What all are the packages available ?
I don't want to connect to the default database that I use for development. I can write code to take up another test database while running a test, but is there any go library that already does it.
Is there any library that does db tests without connecting to database at all ?
What is the standard way to do database test with golang ?
I had a similar question not long ago when refactoring some of my own tests, and there's a couple of ways you can do it:
a) Provide an exported type and an Open or Connect function that returns it - e.g.
type DB struct {
db *sql.DB
}
// Using http://jmoiron.github.io/sqlx/ for this example, but
// it has the same interface as database/sql
func Open(opts *Options) (*DB, error) {
db, err := sqlx.Connect(opts.Driver, fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s", opts.Host, opts.User, opts.Name, opts.SSL))
if err != nil {
return nil, err
}
return &DB{db}, nil
}
... and then each of your tests, write setup & teardown functions that return an instance of *DB that you define your database functions on (as methods - i.e. func (db *DB) GetUser(user *User) (bool, error)):
// Setup the test environment.
func setup() (*DB, error) {
err := withTestDB()
if err != nil {
return nil, err
}
// testOptions is a global in this case, but you could easily
// create one per-test
db, err := Open(testOptions)
if err != nil {
return nil, err
}
// Loads our test schema
db.MustLoad()
return db, nil
}
// Create our test database.
func withTestDB() error {
db, err := open()
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", testOptions.Name))
if err != nil {
return err
}
return nil
}
Note that this is somewhat "integration" testing, but I strongly prefer to test against a "real" database since mocking the interface won't help you catch issues with your queries/query syntax.
b) The alternative, although less extensible on the application side, is to have a global db *sql.DB variable that you initialise in init() within your tests—since tests have no guaranteed order you'll need to use init()—and then run your tests from there. i.e.
var db *sql.DB
func init() {
var err error
// Note the = and *not* the assignment - we don't want to shadow our global
db, err = sqlx.Connect(...)
if err != nil {
...
}
err := db.loadTestSchema
// etc.
}
func TestGetUser(t *testing.T) {
user := User{}
exists, err := db.GetUser(user)
...
}
You can find some practical examples in drone.io's GitHub repo, and I'd also recommend this article on structuring Go applications (especially the DB stuff).
I use a global variable to store the data source (or connection string) of current database and set to different value in test function. Since there is only one database I need to operate so I choose the easiest way.
According to the documentation, it should be possible to retrieve an ancestor and all of its descendants, regardless of their kind.
In my implementation, I have a different kind of ancestor and descendant. The following codes however always returns the error "invalid entity type":
q := datastore.NewQuery("").Ancestor(tomKey)
t := q.Run(ctx)
for {
var x interface{}
_, err := t.Next(&x)
if err == datastore.Done {
break
}
if err != nil {
log.Errorf(ctx, "Error fetching entity: %v", err)
break
}
}
It seems that the call to t.Next(&x) expects a specific type instead of an empty interface. Would somebody please help me to resolve this problem?
I don't know the documentation is wrong, but you can use datastore.PropertyList to fetch arbitrary value. like this:
var v datastore.PropertyList
key, err := iter.Next(&v)
...
props, err := v.Save()
...
See this docs for more information.
I moved a project from the bundled appengine/* imports to google.golang.org/appengine/*. My test cases still rely on appengine/aetest. Unfortunately the aetest package hasn't been ported yet to google.golang.org/appengine/aetest, which is why I get compile errors because it returns a different context type (appengine.Context instead of x/net/context.Context) as the bundled packages.
I also can't create a new context, because I'd need a http.Request object for that.
Is there a way to work around this?
Something like this should work now:
import (
"google.golang.org/appengine"
"google.golang.org/appengine/aetest"
)
func MyTest(t *testing.T) {
inst, err := aetest.NewInstance(nil)
if err != nil {
tb.Fatal(err)
}
req, err := inst.NewRequest("GET", "http://www.whatever.com/", nil)
if err != nil {
tb.Fatal(err)
}
ctx := appengine.NewContext(req)
...
}
Is there a Go analogue to Python/Java's async datastore APIs? Or can one just use the normal API with the go keyword?
There is no Go equivalent to the Python or Java asynchronous APIs for any AppEngine service. In fact, the Go standard library has nothing in the standard asynchronous style either. The reason is that in Go, you write functions using a blocking style and compose them using some basic concurrency primitives based on need. While you cannot just tack go at the beginning of a dastore.Get call, it is still relatively straightforward. Consider the following, contrived example:
func loadUser(ctx appengine.Context, name strings) (*User, err) {
var u User
var entries []*Entry
done := make(chan error)
go func() {
// Load the main features of the User
key := datastore.NewKey(ctx, "user", name, 0, nil)
done <- datastore.Get(ctx, key)
}()
go func() {
// Load the entries associated with the user
q := datastore.NewQuery("entries").Filter("user", name)
keys, err := q.GetAll(ctx, &entries)
for i, k := range keys {
entries[i].key = k
}
done <- err
}()
success := true
// Wait for the queries to finish in parallel
for i := 0; i < 2 /* count the funcs above */; i++ {
if err := <-done; err != nil {
ctx.Errorf("loaduser: %s", err)
success = false
}
}
if !success {
return
}
// maybe more stuff here
}
This same approach can be used in pretty much any context in which you need to run more than one thing that might take awhile at the same time, whether it's a datastore call, urlfetch, file load, etc.
There is no explicit API for async in Go. You should use go routines instead. I haven't seen any source on this, but I suspect the async API isn't there because of how easy it is to use go routines.
I am trying to develop custom User model/authentication code in Go on GAE. The following code is a simple modification of some code in the demos/guestbook application:
q := datastore.NewQuery("User").Filter("Email =", email)
users := make([]User, 0, 1)
if _, err := q.GetAll(c, &users); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
Where email is pulled from a form. It just queries the datastore for a User with the given email. It works fine if the User with the email exists, and dies with an "Internal Server Error" page if they don't. What I don't understand (I guess about error handling in Go, or maybe datastore querying), is why I can't do anything else within that if error block. A slight mod like this:
if _, err := q.GetAll(c, &users); err != nil {
fmt.Fprintf(w, "%s\n", "user not found")
}
produces the same "Internal Server Error" page instead of just printing "user not found".
Thanks!
It is tough to say without seeing the rest of your code, but the main point is that when you say:
if _, err := q.GetAll(c, &users); err != nil {
fmt.Fprintf(w, "%s\n", "user not found")
}
The inner statement will only be triggered if there was an error during the query, not if the query returns nothing. Therefore you aren't actually entering that block of code - my best guess (without seeing the code) is that your Internal Server Error is being triggered elsewhere, perhaps somewhere where you are treating users as a variable that contains data. If you want to print out that message if no user was matched, you could do something simple like check the length of the response - if it is 0, no results were returned and you can print your message:
if len(users) == 0 {
fmt.Fprintf(w, "%s\n", "user not found")
}
There is likely a more idiomatic way, but I believe that will work for your situation (it will print just as you state there - you may want to handle it differently).