Skip to content
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

Manifold method to populate JSON with empty values instead of null #83

Closed
sporkisfaster opened this issue May 10, 2019 · 1 comment
Closed

Comments

@sporkisfaster
Copy link

Is your feature request related to a problem? Please describe.
In testing REST (or Spring-boot) applications, testing controllers with various JSON input is required. For more complex and nested JSON payloads, it would be nice to initialize an instance from a .json file with all of the elements and subelements (i.e. all leaf nodes/elements) with an empty string "" instead of a null value. This way if someone is using parameterized testing, one could only need to worry about the specific elements they care about, use a variable, and in the case of Spock/groovy do something like @unroll over a data field and generate many iterations at will and POST/GET with them (@ParameterizedTest in JUnit being more or less similar).

Describe the solution you'd like
A .createDefault() or .createWithEmpty() method for JSON (and I suppose other manifolds if this could be abstracted out) which would generate JSON values for each element all the way down to the leaf elements with an empty value "". This could fit with either a drop in .json or a schema.

Describe alternatives you've considered
One could just load a JSON from a file with the provided method in the API but this gets clunky as you have to keep track of those files and paths.

Additional context
Add any other context or screenshots about the feature request here.

{
	"pageId": "",
	"imports": {
		"importSupplied": {
			"count": {
				"value": "6"
			}
		},
		"importCustomer": {
			"phoneNumber": {
				"value": ""
			},
			"name": {
				"value": ""
			},
			"zipCode": {
				"value": ""
			},
			"line2Address": {
				"value": ""
			},
			"line1Address": {
				"value": ""
			},
			"city": {
				"value": ""
			},
			"stateCode": {
				"value": ""
			},
			"line3Address": {
				"value": ""
			},
			"id": {
				"value": ""
			}
		}
	},
	"global": {
		"input": "ENTER",
		"exitState": "PROCESSING"
	}
}

Given the above in a resource folder: resources/jsonTests/Customer.json , this feature would allow for the following:

Customer test = Customer.createWithEmpty()

Which would produce:

{
	"pageId": "",
	"imports": {
		"importSupplied": {
			"count": {
				"value": ""
			}
		},
		"importCustomer": {
			"phoneNumber": {
				"value": ""
			},
			"name": {
				"value": ""
			},
			"zipCode": {
				"value": ""
			},
			"line2Address": {
				"value": ""
			},
			"line1Address": {
				"value": ""
			},
			"city": {
				"value": ""
			},
			"stateCode": {
				"value": ""
			},
			"line3Address": {
				"value": ""
			},
			"id": {
				"value": ""
			}
		}
	},
	"global": {
		"input": "",
		"exitState": ""
	}
}

From there testing is now a breeze, just do something like:

@Unroll
def "Try many JSONs with POST"() {
given: "A JSON Manifold object and application request"
Customer test = Customer.createWithEmpty()
Requester<Customer> req = Customer.request("http://api.example.com/customers");

expect: "A successful post is made with the right Page ID"
test.setPageID(pageID)
req.postOne(test) == 'response'

where: "Page IDs are..."
pageID || response
1          ||       'OK'
5         ||       'OK'
42       ||        'NOT FOUND'
@rsmckinney
Copy link
Member

There are a couple of challenges wrt generically creating an "empty" instance:

  1. Size. An object graph can be arbitrarily large; creating empty versions can pose potential performance problems. Similarly, a schema can be recursively defined, which means it can be difficult for a general purpose "empty" graph algorithm to determine when to terminate.
  2. Constraints. A nontrivial schema typically defines "required" and/or non-null properties. An "empty" schema would violate these constraints. Filling these in with valid values can be tricky. For instance, some meta-schemas such as JSON Schema provide sophisticated constraint grammar. Satisfying these constraints with a general purpose algorithm could be problematic.

Perhaps it would be better to tackle the problem differently. Considering the two challenges:

  1. If the idea is to more easily set/get a value within a new object graph at some arbitrary depth, perhaps it's better if the path to the value can be populated lazily. That is:
foo.getOrCreateXxx().getOrCreateYyy().setZzz(Z);

Here the X and Y properties are created on demand if null; getOrCreate always return a non-null value. This works for test purposes since we don't care about the rest of the object graph.
2. While the getOrCreate() approach mitigates the performance problem, it doesn't help with constraint violations. We could add required parameters to the getOrCreate methods, but that's not much of an improvement over the already provided builders. Assuming this is a test-only feature, one way to work around this problem involves an implementation detail employed by most schema-derived type manifolds -- their APIs are structural interfaces backed by DataBindings. Essentially for any path foo.X.Y.setZ(), intermediate properties X and Y can be assigned new DataBindings(). While this blatantly violates constraints, its use is assumed to be targeted for test cases where only specific values are tested and whole object validation is not a consideration.

getOrCreate methods could be added only in test modules. One could build this feature separately using IExtensionClassProducer. The idea is to overlay getOrCreate methods corresponding to all properties in a given schema type (or to all schema types in a given test module). See Generating Extension Classes in the docs.

Eventually this or a similar feature may be provided by Manifold.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants