How to create a random json dictionary with a fixed outer structure for unit testing?

Viewed 105

Using python and hypothesis I want to feed my unit tests with a json dictionary that has a fixed outer structure but a random dictionary as nested payload.

The outer structure needs to be:

{
'id': <some positive integer>,
'kind', <some uppercase string>,
'payload': <nested random valid json dictionary>,
'timestamp': <some datetime stamp>
}

While the payload should be a very simple or very complex dictionary of more or less keys and values that represent typical data: emails, integers, lists, booleans,....

How do I create random deeper dictionaries that are still json-fiable?

1 Answers

You can make a custom strategy that draws recursively from a pool of built-in strategies and use the fixed_dictionaries strategy on the outside:

code of tests.py:

import json
import datetime
from string import ascii_letters, printable

from hypothesis import given, settings
from hypothesis.strategies import text, integers, booleans, emails, floats, dictionaries, recursive, lists, \
    composite, datetimes, none, fixed_dictionaries


@composite
def string_timestamps(draw):
    return str(draw(datetimes(min_value=datetime.datetime(year=2020, month=1, day=1),
                              max_value=datetime.datetime(year=2100, month=12, day=31))))


@composite
def random_lists(draw):
    return draw(recursive(none() | booleans() | floats() | text(printable), lists))


@composite
def nested_dictionaries(draw):
    """
    Builds random nested dictionary structure which is then used as JSON to
    mask two fixed "token" keys.
    """

    random_dict = draw(
        dictionaries(text(),
                     none() | booleans() | integers() | floats() | text(printable) | random_lists() | emails() |
                     dictionaries(text(), none() | booleans() | integers() | floats() | text(printable)),
                     min_size=3,
                     max_size=10))

    return random_dict


event = fixed_dictionaries({
    'id': integers(min_value=1),
    'kind': text(ascii_letters.upper(), min_size=1),
    'payload': nested_dictionaries(),
    'timestamp': string_timestamps(),
})


@given(event)
@settings(max_examples=10)
def test_foo(fixture):
    print(json.dumps(fixture, indent=4, sort_keys=True))

Sample output with pytest -s tests.py:

{
    "id": 17038,
    "kind": "LE",
    "payload": {
        "": [
            null,
            null,
            [
                null,
                false,
                null,
                null,
                true,
                "=um3]MuR2u`",
                null,
                false
            ]
        ],
        "\u000f>": -3.664819488444682e+16,
        "\u00122Y\uda6a\udf67": 1.192092896e-07,
        "=\ud883\udf9bm\u00f5\ud862\udd3b": "#F\u000bUvs'g[C`",
        "c\u00d9\u0018": {
            "\ud937\ude82\u00d0": null
        },
        "\u0098\ud8c9\ude2b\udb4c\udc8e9p\udaa1\udd39": -21914,
        "\u00b2\u00d2\u9521\uda9d\ude69\u00f8\udbf9\udca0\udb9f\udf9a\ud961\udfb5\ud8f7\udc77\ud9c1\ude5a9&\u001b": "Je]#Y",
        "\u00d1\ud996\udc09\u009e\u00a7\u00fc": null,
        "\u00f3\u0080\u00d6": {
            "": "=Qmh+?P'4+~=\u000bU|",
            "6\ud8a2\udd58\u0011\u001a": null,
            ">\u00b3": -25421,
            "X(\udaa6\udceb=n?\u0011\u00f27t\u0012%\u00ee": false,
            "^3b\u0090J\u0094\u00a5h\udb10\ude3b\u0092": -1.4760699148152035e-226,
            "^\u0098W)\u00b0\u00a1\u0090cU\u00c0": null,
            "r": true,
            "s\u00c7\u0013": -78,
            "\u0099": null,
            "\u00f8\u009a\ud85f\udf31\b\u00a4\u00e7\u00a7\u00fdu": 17917,
            "\udaf3\udcba\ud896\ude74\u0000\u00f7\u00ffi\u00d1\ud974\udf92": "tN"
        },
        "\ud85f\udc9e\u00a4\u00c5\ud84f\udfb3\u0097\u6da8": "p-c"
    },
    "timestamp": "2063-11-25 19:41:00.653220"
}
Related