Skip to content

HTTP response assertions

The step from an HTTP response into its parsed body. Duck-typed on the shape every client keeps, so no client library is imported and none has to be installed.

Assertions and steps for an HTTP response.

decoded_as_json

decoded_as_json() -> AssertionBuilder[object]

Parse the response body and return a new builder holding the parsed document.

The step every API test takes, made part of the chain so that what it finds is asserted on with the response still in hand: a failure below this line says which request it came from and what that request answered.

Examples:

Usage:

assert_that(response).decoded_as_json().matches_structure({"id": match.is_positive()})
assert_that(response).decoded_as_json().at_json_path("$.items[0].sku").is_equal_to("A-1")

Returns:

Name Type Description
AssertionBuilder AssertionBuilder[object]

a new instance holding the parsed body. Typed over object rather than over the response, for the reason at_json_path() is typed the way it is: a document is whatever the body held, so a step that cannot know its result must not go on promising the shape of its subject.

Raises:

Type Description
TypeError

if val is not an HTTP response

ValueError

if the body is not JSON, naming the content type and how the body starts

Source code in assertpy2/http_mixin.py
def decoded_as_json(self) -> AssertionBuilder[object]:
    """Parse the response body and return a new builder holding the parsed document.

    The step every API test takes, made part of the chain so that what it finds is asserted on with
    the response still in hand: a failure below this line says which request it came from and what
    that request answered.

    Examples:
        Usage:

            assert_that(response).decoded_as_json().matches_structure({"id": match.is_positive()})
            assert_that(response).decoded_as_json().at_json_path("$.items[0].sku").is_equal_to("A-1")

    Returns:
        AssertionBuilder: a new instance holding the parsed body.  Typed over ``object`` rather than
            over the response, for the reason `at_json_path()` is typed the way it is: a document is
            whatever the body held, so a step that cannot know its result must not go on promising
            the shape of its subject.

    Raises:
        TypeError: if val is not an HTTP response
        ValueError: if the body is not JSON, naming the content type and how the body starts
    """
    response = response_of(self.val)
    if response is None:
        refuse(self.val, "an HTTP response, with a status code and headers")
    try:
        document = _parsed(response)
    except _NoBodyError:
        raise TypeError("the response carries no body this can read, under text, content, data or body") from None
    except Exception as exc:
        # the two together name the usual cause: an expired session answered with a login page
        declared = _content_type(response)
        named = f"content-type is {declared!r}" if declared else "no content type was declared"
        preview = _preview(response)
        if preview is None:
            # nothing is claimed about a body nobody could read: it may well have been JSON
            raise ValueError(f"the response body could not be read: {named}") from exc
        shown = f"it starts with {preview!r}" if preview else "it is empty"
        raise ValueError(f"the response body is not JSON: {named} and {shown}") from exc
    # `builder()` is declared to hand back the same kind, wrong for a step whose result holds the document
    return cast("AssertionBuilder[object]", self.builder(document, self.description, self.kind))