Recently I started writing some tests for sending emails in my Google App Engine Go application. Part of the email sending process is saving the email in the datastore with this function:
func PutInDatastoreFull(c appengine.Context, kind, stringID string, intID int64, parent *datastore.Key, variable interface{}) (*datastore.Key, error) {
k := datastore.NewKey(c, kind, stringID, intID, parent)
key, err := datastore.Put(c, k, variable)
return key, err
}
My tests query the datastore with this function:
func QueryGetAllWithLimit(c appengine.Context, kind string, limit int, dst interface{}) ([]*datastore.Key, error) {
q := datastore.NewQuery(kind).Limit(limit)
return q.GetAll(c, dst)
}
When I run it in development environment, it returns last emails without a problem. However, when I run it with goapp test
, it returns no error and no data. Is this an expected behaviour for the testing environment, or did I just find some bug?
EDIT:
The test is:
//Testing file
func TestEmails(t *testing.T) {
c, err := aetest.NewContext(nil)
if err != nil {
t.Fatal(err)
}
defer c.Close()
err := NotifyAdminOfLowBalance(c, []string{"WARNING1", "WARNING2"})
if err != nil {
t.Fatal(err)
}
emails, err := Data.LoadLastEmails(c, 100)
if err != nil {
t.Fatal(err)
}
if len(emails) == 0 { //test fails here
t.Fatal("len(emails)==0")
}
}
//Production code
func NotifyAdminOfLowBalance(c appengine.Context, warnings []string) error {
not := Notification{}
not.Warnings = warnings
buff := new(bytes.Buffer)
err := LowBalanceTemplate.Execute(buff, not);
if err != nil {
c.Errorf("Backend Notifications - NotifyAdminOfLowBalance - error 1 - %v", err)
return err
}
emailBody := buff.String()
c.Debugf("Backend Notifications - emailBody - %v", emailBody)
emailSubject := "Low balance!"
err = Email.SendHTMLEmail(c, emailSubject, DEFINE.AdminEmails, DEFINE.EmailSender, emailBody)
if err != nil {
c.Errorf("Backend Notifications - NotifyAdminOfLowBalance - error 2 - %v", err)
return err
}
err = Data.CreateAndSaveEmail(c, emailBody, emailSubject, "ADMIN", "", "", "")
if err != nil {
c.Errorf("Backend Notifications - NotifyAdminOfLowBalance - error 3 - %v", err)
return err
}
return nil
}
//Data/Email.go file:
var EmailStr string = "Email"
type Email struct {
//Class for handling Emails of what is happening in the system
EmailID string //ISO date?
Timestamp time.Time
Body string `datastore:"-"`
BodyByte []byte
Subject string
UserEmail string
RippleTxID string
CoinTxID string
Currency string
}
func (l *Email) LoadStrings() {
l.Body = mymath.Hex2ASCII(l.BodyByte)
}
func CreateAndSaveEmail(c appengine.Context, body, subject, userEmail, rippleTx, coinTx, currency string) error {
email := new(Email)
email.Timestamp = time.Now()
email.EmailID = email.Timestamp.Format("2006-01-02T15:04:05:")+mymath.Int2Str(email.Timestamp.Nanosecond())
email.Body = body
email.BodyByte = mymath.ASCII2Hex(body)
email.Subject = subject
email.UserEmail = userEmail
email.RippleTxID = rippleTx
email.CoinTxID = coinTx
email.Currency = currency
keys, err := Datastore.PutInDatastoreFull(c, EmailStr, email.EmailID, 0, nil, email)
c.Debugf("keys - %v, err - %v", keys, err)
if err != nil {
c.Errorf("Email - CreateAndSaveEmail error 1 - %v", err)
return err
}
return nil
}
func LoadLastEmails(c appengine.Context, count int) ([]Email, error) {
c.Debugf("LoadLastEmails - %v", count)
dst := []Email{}
keys, err := Datastore.QueryGetAllWithLimit(c, EmailStr, count, &dst)
c.Debugf("keys - %v, err - %v", keys, err)
if err != nil {
c.Errorf("Email - LoadLastEmails error 1 - %v", err)
return nil, err
}
c.Debugf("dst - %v", dst)
return dst, nil
}
And the Datastore functions were already included above
I ran into this too and I believe it to be because you are doing a cross group query and the write has not been applied yet. I was able to reproduce this with the dev server too if I made a put then a query right after. I understand this to be how the dev server simulates production-like write visibility when you are not in a transaction or entity group. IOW, I think it's working as it should.
Adding a sleep between your calls (a bad idea) or performing a get() on any of the entities that had previously changed/created in the test will make them show up in the query.
For writes and data visibility rules see: https://developers.google.com/appengine/docs/go/datastore/#Go_Datastore_writes_and_data_visibility