Repository: gobuffalo/buffalo (language: go)

Issue:
Panic in Simpleworker
Hello,

I'm getting panic in Debug logger when a simple worker completes the job 
```
level=debug time="2022-04-17T22:20:12Z" msg="completed job %!s(PANIC=String method: runtime error: invalid memory address or nil pointer dereference)
```

That's the [culprit](https://github.com/gobuffalo/buffalo/blob/main/worker/simple.go#L103) and this  [code panics too](https://github.com/gobuffalo/buffalo/blob/main/worker/simple.go#L81)




Discussion / hints:
@sio4 , hopefully, a fix will land in [v0.18.6](https://github.com/gobuffalo/buffalo/pull/2239) :) 
Aha... so did you get the message for "performing job..."  at line 81 too? If then, it could be an issue with calling the function. There is no `nil` checking, so I will add that as soon as possible (my working hours just start within 10 min so maybe this evening)

If you can see the "performing job..." line correctly, but see panic for the "completed job..." line, it could be another issue. In this case, please let me know what was your job definition and if this issue is easily reproducible. Providing steps for reproducing or sample application could be the best.

https://github.com/gobuffalo/buffalo/blob/e8274e66e2e725f84f29e28dc1ca994fe59677fe/worker/simple.go#L80-L111
@sio4, Yes, it throws the same error in both lines.  

No worries. Thanks for taking the time in solving this :) 
I took a look at the issue but I could not reproduce the issue and I feel like there is no reason for the panic on that part.

The below is the definition of `Job` and it should not be a `nil` in any case, and the `String()` function should work even if the structure is empty. Could you please share some more of your code, especially the Job you submitted?

https://github.com/gobuffalo/buffalo/blob/e8274e66e2e725f84f29e28dc1ca994fe59677fe/worker/job.go#L14-L26

@sio4, Yes, weirdly, it's throwing a panic in the string. An improvement will be actually checking the error returned by ```json.Marshal``` and not ignore them. If there's an error then just return an empty string and use the logger to spit out the error.

I will try to marshal that Job and see if it panics. It could be an underlying data structure causing this, and it might be a proto issue. 
No, what I understand from the error message is that the panic is on **executing the `String()` against the `Job`** but not on the inside of the `String()` function. It seems like the `Job` you passed to the `Perform` was a `nil` which is not possible in normal cases.

So I want to see your code block of the Job definition and how it calls the `Perform` function to verify my extreme theory. The only possible situation I can imagine now is that you did something like:

```go
	var j *Job
	w.Perform(*j)
```

but maybe not. Right?

In this case, I declare the variable `j` without memory allocation and passed it as an argument of `Perform` in the wrong way.

For the panic on `String()`, please check this playground code: https://go.dev/play/p/G_qUrWgwGyf  for your information.
By the way, what are your versions? Could you provide the output of `buffalo info`?
@sio4 , The job isn't a pointer, and the job does execute and fans out the notification as intended.

```
w.Perform(worker.Job{
		Queue:   "default",
		Handler: "push_notification",
		Args: worker.Args{
			"context": c,
			"r":       re.r,
			"u":       req,
		},
	})
```

I checked all of the Arguments none of them are nil. If any is nill, then it won't be able to perform the job, and it safely returns
Buffalo Version:
```
require (
        github.com/gobuffalo/buffalo v0.18.5
        github.com/gobuffalo/envy v1.10.1
        github.com/gobuffalo/logger v1.0.6
        github.com/gobuffalo/mw-csrf v1.0.0
        github.com/gobuffalo/mw-forcessl v0.0.0-20200131175327-94b2bd771862
        github.com/gobuffalo/mw-i18n v1.1.0
        github.com/gobuffalo/mw-paramlogger v1.0.0
        github.com/gobuffalo/packr/v2 v2.8.1
        github.com/gobuffalo/plush/v4 v4.1.10-0.20220324170703-6c2169a56f63
        github.com/gobuffalo/suite/v3 v3.0.2
        github.com/gobuffalo/tags v2.1.7+incompatible
)

require(
     github.com/gobuffalo/events v1.4.2 // indirect
        github.com/gobuffalo/fizz v1.14.0 // indirect
        github.com/gobuffalo/flect v0.2.4 // indirect
        github.com/gobuffalo/github_flavored_markdown v1.1.1 // indirect
        github.com/gobuffalo/helpers v0.6.4 // indirect
        github.com/gobuffalo/httptest v1.5.1 // indirect
        github.com/gobuffalo/meta v0.3.1 // indirect
        github.com/gobuffalo/nulls v0.4.1 // indirect
        github.com/gobuffalo/packd v1.0.1 // indirect
        github.com/gobuffalo/pop/v5 v5.3.4 // indirect
        github.com/gobuffalo/pop/v6 v6.0.1 // indirect
        github.com/gobuffalo/tags/v3 v3.1.2 // indirect
        github.com/gobuffalo/validate/v3 v3.3.1 // indirect
)
```
Oh, that is much more interesting. So the job worked without any issue but those two debugging outputs had panic?

What is your OS, system architecture, and golang version? also, please post your output of `buffalo info` to check any hints.
@sio4, Yes, the job worked fine. That's the weird part. And it's not a data race issue. I thought maybe for some reason it gets fired twice. One with a job and the other Nil, but that wasn't the case. The job ran once. 

OS: ubuntu 18.04 x64
Golang: 1.18.1. I tested it with Go 1.17 and it threw the same error 


```
-> Go: Checking installation
✓ The `go` executable was found on your system at: /usr/local/go/bin/go

-> Go: Checking minimum version requirements
✓ Your version of Go, 1.14.13, meets the minimum requirements.

-> Go: Checking Package Management
✓ You are using Go Modules (`go`) for package management.

-> Go: Checking PATH
✓ Your PATH contains REMOVED/go/bin.

-> Node: Checking installation
✓ The `node` executable was found on your system at: /usr/local/bin/node

-> Node: Checking minimum version requirements
✓ Your version of Node, v14.16.0, meets the minimum requirements.

-> NPM: Checking installation
✓ The `npm` executable was found on your system at: /usr/local/bin/npm

-> NPM: Checking minimum version requirements
✓ Your version of NPM, 8.1.4, meets the minimum requirements.

-> Yarn: Checking installation
✓ The `yarnpkg` executable was found on your system at: /usr/bin/yarnpkg

-> Yarn: Checking minimum version requirements
✓ Your version of Yarn, 1.21.1, meets the minimum requirements.

-> PostgreSQL: Checking installation
✘ The `postgres` executable could not be found on your system.
For help setting up your Postgres environment please follow the instructions for you platform at:

https://www.postgresql.org/download/

-> MySQL: Checking installation
✓ The `mysql` executable was found on your system at: /usr/bin/mysql

-> MySQL: Checking minimum version requirements
✓ Your version of MySQL, 8.0.28, meets the minimum requirements.

-> SQLite3: Checking installation
✘ The `sqlite3` executable could not be found on your system.
For help setting up your SQLite3 environment please follow the instructions for you platform at:

https://www.sqlite.org/download.html

-> Cockroach: Checking installation
✘ The `cockroach` executable could not be found on your system.
For help setting up your Cockroach environment please follow the instructions for you platform at:

https://www.cockroachlabs.com/docs/stable/

-> Buffalo (CLI): Checking installation
✓ The `buffalo` executable was found on your system at: /usr/local/bin/buffalo

-> Buffalo (CLI): Checking minimum version requirements
✓ Your version of Buffalo (CLI), v0.16.21, meets the minimum requirements.

-> Buffalo: Application Details
Pwd         REMOVED
Root        REMOVED
GoPath      REMOVED/go
PackagePkg  REMOVED
ActionsPkg  REMOVED/actions
ModelsPkg   REMOVED/models
GriftsPkg   REMOVED/grifts
WithModules true
Name        REMOVED
Bin         bin/REMOVED
VCS         git
WithPop     true
WithSQLite  false
WithDep     false
WithWebpack false
WithNodeJs  false
WithYarn    true
WithDocker  true
WithGrifts  true
AsWeb       true
AsAPI       false
InApp       true
PackageJSON {map[]}

-> Buffalo: config/buffalo-app.toml
name = "REMOVED"
bin = "bin/REMOVED"
vcs = "git"
with_pop = true
with_sqlite = false
with_dep = false
with_webpack = false
with_nodejs = false
with_yarn = true
with_docker = true
with_grifts = true
as_web = true
as_api = false

-> Buffalo: config/buffalo-plugins.toml
[[plugin]]
  binary = "buffalo-pop"
  go_get = "github.com/gobuffalo/buffalo-pop/v2"

-> Buffalo: go.mod
module REMOVED

go 1.17

require (
        bitbucket.org/REMOVED/helpers/money v0.0.0-20220214114623-8e66da081c2b
        bitbucket.org/REMOVED/helpers/redis/emails v0.0.0-20220416153634-fc4c1e8cbf6f
        bitbucket.org/REMOVED/helpers/redis/templates v0.0.0-20210917145841-c33787a84524
        bitbucket.org/REMOVED/proto v0.0.0-20220417112620-921301107f9a
        github.com/go-playground/validator/v10 v10.9.0
        github.com/go-redis/redis/v8 v8.11.5
        github.com/gobuffalo/buffalo v0.18.5
        github.com/gobuffalo/envy v1.10.1
        github.com/gobuffalo/logger v1.0.6
        github.com/gobuffalo/mw-csrf v1.0.0
        github.com/gobuffalo/mw-forcessl v0.0.0-20200131175327-94b2bd771862
        github.com/gobuffalo/mw-i18n v1.1.0
        github.com/gobuffalo/mw-paramlogger v1.0.0
        github.com/gobuffalo/packr/v2 v2.8.1
        github.com/gobuffalo/plush/v4 v4.1.10-0.20220324170703-6c2169a56f63
        github.com/gobuffalo/suite/v3 v3.0.2
        github.com/gobuffalo/tags v2.1.7+incompatible
        github.com/golang/protobuf v1.5.2
        github.com/gorilla/sessions v1.2.1
        github.com/markbates/grift v1.5.0
        github.com/pkg/errors v0.9.1
        github.com/sirupsen/logrus v1.8.1
        github.com/spf13/viper v1.11.0
        github.com/stretchr/testify v1.7.1
        github.com/unrolled/secure v1.0.9
        google.golang.org/grpc v1.45.0
        google.golang.org/protobuf v1.28.0
)

require (
        bitbucket.org/REMOVED/app_error v0.0.0-20220416134420-59e77bd715a7 // indirect
        github.com/BurntSushi/toml v1.0.0 // indirect
        github.com/Masterminds/semver/v3 v3.1.1 // indirect
        github.com/Rhymond/go-money v1.0.3 // indirect
        github.com/aymerick/douceur v0.2.0 // indirect
        github.com/cespare/xxhash/v2 v2.1.2 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
        github.com/dustin/go-humanize v1.0.0 // indirect
        github.com/fatih/color v1.13.0 // indirect
        github.com/fatih/structs v1.1.0 // indirect
        github.com/felixge/httpsnoop v1.0.1 // indirect
        github.com/fsnotify/fsnotify v1.5.1 // indirect
        github.com/go-playground/locales v0.14.0 // indirect
        github.com/go-playground/universal-translator v0.18.0 // indirect
        github.com/go-sql-driver/mysql v1.6.0 // indirect
        github.com/gobuffalo/events v1.4.2 // indirect
        github.com/gobuffalo/fizz v1.14.0 // indirect
        github.com/gobuffalo/flect v0.2.4 // indirect
        github.com/gobuffalo/github_flavored_markdown v1.1.1 // indirect
        github.com/gobuffalo/helpers v0.6.4 // indirect
        github.com/gobuffalo/httptest v1.5.1 // indirect
        github.com/gobuffalo/meta v0.3.1 // indirect
        github.com/gobuffalo/nulls v0.4.1 // indirect
        github.com/gobuffalo/packd v1.0.1 // indirect
        github.com/gobuffalo/pop/v5 v5.3.4 // indirect
        github.com/gobuffalo/pop/v6 v6.0.1 // indirect
        github.com/gobuffalo/tags/v3 v3.1.2 // indirect
        github.com/gobuffalo/validate/v3 v3.3.1 // indirect
        github.com/gofrs/uuid v4.1.0+incompatible // indirect
        github.com/gofrs/uuid/v3 v3.1.2 // indirect
        github.com/gorilla/css v1.0.0 // indirect
        github.com/gorilla/handlers v1.5.1 // indirect
        github.com/gorilla/mux v1.8.0 // indirect
        github.com/gorilla/securecookie v1.1.1 // indirect
        github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
        github.com/hashicorp/hcl v1.0.0 // indirect
        github.com/inconshreveable/mousetrap v1.0.0 // indirect
        github.com/jackc/chunkreader/v2 v2.0.1 // indirect
        github.com/jackc/pgconn v1.10.1 // indirect
        github.com/jackc/pgio v1.0.0 // indirect
        github.com/jackc/pgpassfile v1.0.0 // indirect
        github.com/jackc/pgproto3/v2 v2.1.1 // indirect
        github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
        github.com/jackc/pgtype v1.8.1 // indirect
        github.com/jackc/pgx/v4 v4.13.0 // indirect
        github.com/jmoiron/sqlx v1.3.4 // indirect
        github.com/joho/godotenv v1.4.0 // indirect
        github.com/karrick/godirwalk v1.16.1 // indirect
        github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
        github.com/leodido/go-urn v1.2.1 // indirect
        github.com/luna-duclos/instrumentedsql v1.1.3 // indirect
        github.com/magiconair/properties v1.8.6 // indirect
        github.com/markbates/errx v1.1.0 // indirect
        github.com/markbates/oncer v1.0.0 // indirect
        github.com/markbates/refresh v1.12.0 // indirect
        github.com/markbates/safe v1.0.1 // indirect
        github.com/mattn/go-colorable v0.1.12 // indirect
        github.com/mattn/go-isatty v0.0.14 // indirect
        github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
        github.com/microcosm-cc/bluemonday v1.0.16 // indirect
        github.com/mitchellh/go-homedir v1.1.0 // indirect
        github.com/mitchellh/mapstructure v1.4.3 // indirect
        github.com/monoculum/formam v3.5.5+incompatible // indirect
        github.com/pelletier/go-toml v1.9.4 // indirect
        github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/rogpeppe/go-internal v1.8.0 // indirect
        github.com/sergi/go-diff v1.2.0 // indirect
        github.com/shopspring/decimal v1.2.1-0.20210622222456-cc4584f0a5de // indirect
        github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
        github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
        github.com/spf13/afero v1.8.2 // indirect
        github.com/spf13/cast v1.4.1 // indirect
        github.com/spf13/cobra v1.4.0 // indirect
        github.com/spf13/jwalterweatherman v1.1.0 // indirect
        github.com/spf13/pflag v1.0.5 // indirect
        github.com/subosito/gotenv v1.2.0 // indirect
        golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
        golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
        golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
        golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
        golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
        golang.org/x/text v0.3.7 // indirect
        google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect
        gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
        gopkg.in/ini.v1 v1.66.4 // indirect
        gopkg.in/yaml.v2 v2.4.0 // indirect
        gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
```

> I thought maybe for some reason it gets fired twice. One with a job and the other Nil, but that wasn't the case. The job ran once.

Yeah, I thought similar situation but since we don't have proper logs, cannot prove it. :-( How about adding some application-level logs when calling `Perform`? such as `logger.Debugf("calling perform with %s", job)`

> OS: ubuntu 18.04 x64 Golang: 1.18.1. I tested it with Go 1.17 and it threw the same error

By the way, you mention that your go version is 1.18.1, and also tested with 1.17, but from the output of `buffalo info`, it seems like the actual version of golang is 1.14.13. (see below, and please check that again in your env) Also buffalo cli version is too old too. Is this information correct?

> ```
> -> Go: Checking installation
> ✓ The `go` executable was found on your system at: /usr/local/go/bin/go
> 
> -> Go: Checking minimum version requirements
> ✓ Your version of Go, 1.14.13, meets the minimum requirements.
> <...>
> -> Buffalo (CLI): Checking minimum version requirements
> ✓ Your version of Buffalo (CLI), v0.16.21, meets the minimum requirements.
> <...>
> ```


@sio4 , 
yes, it's weird that it's reporting Go ```1.14.13```. The only env I got is Go ```1.18.1```. Not sure why the tool not reporting correctly the go version. 

So I found the culprit and as expected it was due to the [sync.Map](https://github.com/gobuffalo/buffalo/blob/main/default_context.go#L36) in the default_context being nil. 


```
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47b166]

goroutine 141 [running]:
encoding/json.(*encodeState).marshal.func1()
	/usr/local/go/src/encoding/json/encode.go:328 +0x6e
panic({0xfa2120, 0x1ad21d0})
	/usr/local/go/src/runtime/panic.go:1038 +0x215
sync.(*Map).Range(0x1, 0xc00023d268)
	/usr/local/go/src/sync/map.go:324 +0x26
github.com/gobuffalo/buffalo.(*DefaultContext).Data(0xc0002763f0)
	/REMOVED/pkg/mod/github.com/gobuffalo/buffalo@v0.18.5/default_context.go:238 +0x50
github.com/gobuffalo/buffalo.(*DefaultContext).MarshalJSON(0x10b95e0)
	/REMOVED/pkg/mod/github.com/gobuffalo/buffalo@v0.18.5/default_context.go:279 +0x3f
encoding/json.marshalerEncoder(0xc000713e80, {0x10b95e0, 0xc0002763f0, 0x492169}, {0x0, 0x30})
	/usr/local/go/src/encoding/json/encode.go:479 +0xbe
encoding/json.(*encodeState).reflectValue(0x10865a0, {0x10b95e0, 0xc0002763f0, 0xc00023d470}, {0xf2, 0x3f})
	/usr/local/go/src/encoding/json/encode.go:360 +0x78
encoding/json.interfaceEncoder(0xc000713e80, {0x10865a0, 0xc000668890, 0x30}, {0x7a, 0xf2})
	/usr/local/go/src/encoding/json/encode.go:716 +0xc8
encoding/json.structEncoder.encode({{{0xc000867ef0, 0x40ff30, 0x10}, 0xc000c39320}}, 0xc000713e80, {0x10c1a00, 0xc000668880, 0xc000466b70}, {0x0, 0x1})

```

We had similar issues in the past if we wanted to access [ctx.Value](https://github.com/gobuffalo/buffalo/blob/main/default_context.go#L75) with a string key when the default_context is manually  created and not through an HTTP request.   
@sio4 , maybe a better fix is to do something similar to [this](https://github.com/gobuffalo/buffalo/blob/main/default_context.go#L277) 
Oh, Got it. So you passed the `buffalo.Context` to the worker as an argument. Now I understand what happens.

```go
w.Perform(worker.Job{
		Queue:   "default",
		Handler: "push_notification",
		Args: worker.Args{
			"context": c,
			"r":       re.r,
			"u":       req,
		},
	})
```

However, if it's the case, the issue is on your usage of the background workers. The argument should be able to be serialized in JSON format, and also it should not be a memory reference (should be a kind of call by value). The reason is simple. The concept of the background worker is "distributed long-running task execution on the other machine" even though the  Simple worker itself is designed to run the job in the same process in a new goroutine.

Currently, your job works fine since the memories you passed to the worker are still accessible by goroutine in the same process. However, if you extend your service and want to run those long-running tasks on separated machines, and want to replace the worker with another worker that supports remote queueing via message queues such as https://github.com/gobuffalo/gocraft-work-adapter, https://github.com/stanislas-m/amqp-work-adapter, or whatever, they will not work since your arguments cannot be serialized as a JSON message which is needed to be passed to the other machine via a message queue. (you should be free to replace any worker adapter implementation without code change which calls `Perform`.)

For flexibility, the `Arg` is simply defined as `type Args map[string]interface{}` but users should make sure their argument is able to be converted as JSON marshaled string.

https://github.com/gobuffalo/buffalo/blob/e8274e66e2e725f84f29e28dc1ca994fe59677fe/worker/job.go#L5-L11

So the solution is that you should collect which information is needed for the job (e.g. user id, message to the user, or database key for the additional information), add those values to your own Arg structure, make sure the Arg is able to be marshaled as JSON safely, then pass the Arg to the job.

I think I need to consider improving our document for the part, and also try to find a way to verify the arguments programmatically too.

Can it be the answer?

Fix patch:
diff --git a/default_context.go b/default_context.go
index 3f2eec99d..e88acbf4b 100644
--- a/default_context.go
+++ b/default_context.go
@@ -23,6 +23,9 @@ import (
 var _ Context = &DefaultContext{}
 var _ context.Context = &DefaultContext{}
 
+// TODO(sio4): #road-to-v1 - make DefaultContext private
+// and only allow it to be generated by App.newContext() or any similar.
+
 // DefaultContext is, as its name implies, a default
 // implementation of the Context interface.
 type DefaultContext struct {
@@ -67,16 +70,22 @@ func (d *DefaultContext) Param(key string) string {
 // Set a value onto the Context. Any value set onto the Context
 // will be automatically available in templates.
 func (d *DefaultContext) Set(key string, value interface{}) {
+	if d.data == nil {
+		d.data = &sync.Map{}
+	}
 	d.data.Store(key, value)
 }
 
 // Value that has previously stored on the context.
 func (d *DefaultContext) Value(key interface{}) interface{} {
-	if k, ok := key.(string); ok {
+	if k, ok := key.(string); ok && d.data != nil {
 		if v, ok := d.data.Load(k); ok {
 			return v
 		}
 	}
+	if d.Context == nil {
+		return nil
+	}
 	return d.Context.Value(key)
 }
 
@@ -235,6 +244,11 @@ func (d *DefaultContext) Redirect(status int, url string, args ...interface{}) e
 // Data contains all the values set through Get/Set.
 func (d *DefaultContext) Data() map[string]interface{} {
 	m := map[string]interface{}{}
+
+	if d.data == nil {
+		return m
+	}
+
 	d.data.Range(func(k, v interface{}) bool {
 		s, ok := k.(string)
 		if !ok {


Test patch:
diff --git a/default_context_test.go b/default_context_test.go
index 2823828cc..203781641 100644
--- a/default_context_test.go
+++ b/default_context_test.go
@@ -143,6 +143,15 @@ func Test_DefaultContext_GetSet(t *testing.T) {
 	r.Equal("Mark", c.Value("name").(string))
 }
 
+func Test_DefaultContext_Set_Unconfigured(t *testing.T) {
+	r := require.New(t)
+	c := DefaultContext{}
+
+	c.Set("name", "Yonghwan")
+	r.NotNil(c.Value("name"))
+	r.Equal("Yonghwan", c.Value("name").(string))
+}
+
 func Test_DefaultContext_Value(t *testing.T) {
 	r := require.New(t)
 	c := basicContext()
@@ -151,7 +160,12 @@ func Test_DefaultContext_Value(t *testing.T) {
 	c.Set("name", "Mark")
 	r.NotNil(c.Value("name"))
 	r.Equal("Mark", c.Value("name").(string))
-	r.Equal("Mark", c.Value("name").(string))
+}
+
+func Test_DefaultContext_Value_Unconfigured(t *testing.T) {
+	r := require.New(t)
+	c := DefaultContext{}
+	r.Nil(c.Value("name"))
 }
 
 func Test_DefaultContext_Render(t *testing.T) {
@@ -301,3 +315,68 @@ func Test_DefaultContext_Bind_JSON(t *testing.T) {
 
 	r.Equal("Mark", user.FirstName)
 }
+
+func Test_DefaultContext_Data(t *testing.T) {
+	r := require.New(t)
+	c := basicContext()
+
+	r.EqualValues(map[string]interface{}{}, c.Data())
+}
+
+func Test_DefaultContext_Data_Unconfigured(t *testing.T) {
+	r := require.New(t)
+	c := DefaultContext{}
+
+	r.EqualValues(map[string]interface{}{}, c.Data())
+}
+
+func Test_DefaultContext_String(t *testing.T) {
+	r := require.New(t)
+	c := basicContext()
+	c.Set("name", "Buffalo")
+	c.Set("language", "go")
+
+	r.EqualValues("language: go\n\nname: Buffalo", c.String())
+}
+
+func Test_DefaultContext_String_EmptyData(t *testing.T) {
+	r := require.New(t)
+	c := basicContext()
+	r.EqualValues("", c.String())
+}
+
+func Test_DefaultContext_String_EmptyData_Unconfigured(t *testing.T) {
+	r := require.New(t)
+	c := DefaultContext{}
+
+	r.EqualValues("", c.String())
+}
+
+func Test_DefaultContext_MarshalJSON(t *testing.T) {
+	r := require.New(t)
+	c := basicContext()
+	c.Set("name", "Buffalo")
+	c.Set("language", "go")
+
+	jb, err := c.MarshalJSON()
+	r.NoError(err)
+	r.EqualValues(`{"language":"go","name":"Buffalo"}`, string(jb))
+}
+
+func Test_DefaultContext_MarshalJSON_EmptyData(t *testing.T) {
+	r := require.New(t)
+	c := basicContext()
+
+	jb, err := c.MarshalJSON()
+	r.NoError(err)
+	r.EqualValues(`{}`, string(jb))
+}
+
+func Test_DefaultContext_MarshalJSON_EmptyData_Unconfigured(t *testing.T) {
+	r := require.New(t)
+	c := DefaultContext{}
+
+	jb, err := c.MarshalJSON()
+	r.NoError(err)
+	r.EqualValues(`{}`, string(jb))
+}