svanoort / pyresttest

Python Rest Testing
Apache License 2.0
1.15k stars 326 forks source link

Create a validator extension for validating Json Array #155

Closed pshokeen closed 8 years ago

pshokeen commented 8 years ago

Im trying to create a json array validator. In a nutshell, the problem is my web response is a straight up json array from a search query. And i have to validate a property on each object. For example, whether each object's username contains a substring value.

how would the parse function be used exactly.

Do i have to define a separate "contains" check for each time i want to do a comparison on a property in the json object, or can i use the existing extract_test/jsonpath_mini, contains comparators?

svanoort commented 8 years ago

@pshokeen Hi there! There are two ways to accomplish this with PyRestTest:

  1. Use the jsonpath_mini extractors to pull out the individual elements, then do a contains operator on each
  2. Write a small custom comparator function that does the for-each and tests each element against something (with a comparator, you can supply a string to compare against). This can be loaded as an extension at runtime. You can use the jsonpath_mini extractor to get just the array, to make your python code simpler.

For examples and information, see the extensions guide

Doing a comparator extension will be simpler than a full validator extension, because you only need a function that takes two inputs - in your case, the python array from the jsonpath_mini extractor, and the expression to test with.

If you want a full validator, the parse function takes a configuration object (python, extracted from YAML) and then returns a configured instance of the validator.

Does that answer your question?

pshokeen commented 8 years ago

@svanoort Somewhat. Im have two prototypes for doing this right now.

Prototype 1:

Extension.py:

 def json_array_contains(config):
    dictArray = json.loads(config['body'])
    keyList = config['keys']
    value = config['value']

    for item in dictArray:
        flag = False

        for key in keyList:
            if value.lower() in str(item.get(key, [])).lower():
                flag = True
                break

        if not flag:
            return False
    return True

 VALIDATORS = {
    'json_contains': json_array_contains
 }

Test.yaml This is where im unsure of how to use this

Want to validate that each element in the json contains a key called username and has test as the substring of the value

- validators:
    - json_contains: lambda(body, 'username', 'test')

The test before this simply does a rest call to an endpoint with some query parameters and the response body is a json array of objects. Im unsure of how to PASS in the response body to the validator. Because when i simply do body like this, it passes in the text "body"

{'body': 'body', 'keys': ['username'], 'value': 'test'}

And expectedly i get the exception

raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

Prorotype 2 is lambda expressions, but i'd rather understand this first.

svanoort commented 8 years ago

@pshokeen That's a start but I there is some confusion about how extensions and tests work. You can't use a lambda in the YAML (it's not allowed to be fully executable). You'll have to parse something from the config for the validator to configure your lambda if you do that.

For a validator, what is actually invoked is the 'validate' function: def validate(self, body=None, headers=None, context=None). Your validator must have validate and parse functions

Here's one approach using comparators (hacky but ought to work):


def all_elements_contain_value_in_string(json_array, config):
    key = config['key']
    expected_val = config['value']

    if not isinstance(json_array, dict):
        return False
    try:
        for item in json_array:
            val = item[key]
            if expected_val no in val:
                return False
        return True
    except KeyError:
        return False

 COMPARATOR = {
    'all_elements_contain_value_in_string': all_elements_contain_value_in_string
 }

Test syntax

 - validators:
    - compare: {jsonpath_mini: '.', comparator:'all_elements_contain_value_in_string', expected: {'key':'username', 'value': 'test'}}

This is the quick-and-dirty solution since you don't get the proper logging and failure reasons that validators provide.

pshokeen commented 8 years ago

Got it working. Thanks for the advice @svanoort

kite-nubia commented 8 years ago

@svanoort https://github.com/svanoort/pyresttest/blob/master/sample_extension.py here, i just wanna try to use your sample validator "is_dict", i change "is_dict" to "is_notnull_list". def is_notnull_list(input): return input is not None then, i try to use it in this way: