pytest_case.case.case

case(name_or_gen: Union[str, Iterable[Any]], *args: Any, **kwargs: Any) -> Callable[..., Any]

Decorator to define test cases for pytest.

The case decorator is used to create parameterized test cases for pytest. It lets the same test function run with different configurations while maintaining clear test case identification.

Parameters:
  • name_or_gen (Union[str, Iterable[Any]]) –
    • If a string, specifies the unique identifier (ID) for the test case.
    • If an iterable, enables the creation of generator-based test cases, allowing multiple sets of parameters to be defined dynamically.
  • *args (Any, default: () ) –

    Positional arguments to be passed as parameters to the decorated test function. These are mapped to the function's required arguments.

  • **kwargs (Any, default: {} ) –

    Keyword arguments to be passed as parameters to the decorated test function. These are matched by name with the function's parameters. Special keyword argument marks can be used to specify pytest marks (e.g., @pytest.mark.skip). If provided as part of an iterable name_or_gen parameter, the name keyword will be used to represent a template string for each generated test case's name (e.g., "test for {}").

Returns:
  • Callable[..., Any]

    Callable[..., Any]: The decorated function.

Behavior
  • If the name_or_gen argument is a string, the decorator creates a single test case with the specified ID.
  • If name_or_gen is an iterable, it generates multiple test cases dynamically, each corresponding to a combination of the iterable values and the input arguments. In that case (pun intended), the name keyword is used to represent a template string for each test cases' name (e.g., "test {}").
Advanced Features
  1. Marks Support: Use the marks keyword in kwargs to attach pytest marks to a specific test case. Marks can be used in the same way as with pytest marks

  2. Existing Case Wrapping: When applied to an already-decorated function, the case decorator unwraps its parameters and arguments, allowing additional cases or modifications without conflicts.

  3. Default Argument Handling: Automatically integrates function defaults with provided arguments.

Examples:

>>> import pytest
>>> from pytest_case import case
>>>
>>> @case("valid_credentials", "root", "toor")
>>> @case("invalid_credentials", "user123", "password456", marks=pytest.mark.xfail)
>>> def test_login(username: str, password: str) -> None:
>>>     assert login(username, password)
Notes
  • The decorator validates the provided arguments to ensure compatibility with the target function and raises errors for invalid or missing inputs.
  • For complex cases, use the generator-based approach (first parameter as an iterable) to simplify dynamic test creation and avoid redundant repetition.
Source code in pytest_case/case.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def case(name_or_gen: Union[str, Iterable[Any]], *args: Any, **kwargs: Any) -> Callable[..., Any]:
    """
    Decorator to define test cases for pytest.

    The `case` decorator is used to create parameterized test cases for pytest.
    It lets the same test function run with different configurations while maintaining
    clear test case identification.

    Args:
        name_or_gen (Union[str, Iterable[Any]]):
            - If a string, specifies the unique identifier (ID) for the test case.
            - If an iterable, enables the creation of generator-based test cases, allowing
              multiple sets of parameters to be defined dynamically.
        *args (Any): Positional arguments to be passed as parameters to the decorated test
                    function. These are mapped to the function's required arguments.
        **kwargs (Any): Keyword arguments to be passed as parameters to the decorated test
                        function. These are matched by name with the function's parameters.
                        Special keyword argument `marks` can be used to specify pytest marks
                        (e.g., `@pytest.mark.skip`).
                        If provided as part of an iterable `name_or_gen` parameter, the `name`
                        keyword will be used to represent a template string for each generated test
                        case's name (e.g., "test for {}").

    Returns:
        Callable[..., Any]: The decorated function.

    Behavior:
        - If the `name_or_gen` argument is a string, the decorator creates a single test case with
          the specified ID.
        - If `name_or_gen` is an iterable, it generates multiple test cases dynamically, each
          corresponding to a combination of the iterable values and the input arguments.
          In that case (pun intended), the `name` keyword is used to represent a template string
          for each test cases' name (e.g., "test {}").

    Advanced Features:
        1. **Marks Support**:
           Use the `marks` keyword in `kwargs` to attach pytest marks to a specific
           test case. Marks can be used in the same way as with
           [pytest marks](https://docs.pytest.org/en/stable/reference/reference.html#marks)

        2. **Existing Case Wrapping**:
           When applied to an already-decorated function, the `case` decorator unwraps its
           parameters and arguments, allowing additional cases or modifications without conflicts.

        3. **Default Argument Handling**:
           Automatically integrates function defaults with provided arguments.

    Examples:
        >>> import pytest
        >>> from pytest_case import case
        >>>
        >>> @case("valid_credentials", "root", "toor")
        >>> @case("invalid_credentials", "user123", "password456", marks=pytest.mark.xfail)
        >>> def test_login(username: str, password: str) -> None:
        >>>     assert login(username, password)

    Notes:
        - The decorator validates the provided arguments to ensure compatibility with the
          target function and raises errors for invalid or missing inputs.
        - For complex cases, use the generator-based approach (first parameter as an iterable)
          to simplify dynamic test creation and avoid redundant repetition.
    """
    marks = kwargs.pop(MARKS_PARAM_NAME, None) or tuple()

    def decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
        # Can't check for `Iterable` because `str` is also iterable
        if not isinstance(name_or_gen, str):
            return _generator_case(name_or_gen, func, **kwargs)
        name = name_or_gen

        validate_case_inputs(func, args, kwargs)

        argnames = []
        defaults: Dict[str, Any] = {}
        case_param_sets: List[ParameterSet] = []

        if is_case(func):
            # If wrapping an existing case
            unwrapped_func_attrs = unwrap_func(func)
            func = unwrapped_func_attrs.unwrapped_func
            argnames = unwrapped_func_attrs.argnames
            case_param_sets = unwrapped_func_attrs.argvalues
            defaults = unwrapped_func_attrs.defaults
        else:
            # If wrapping a new test function
            func_params = get_func_param_names(func)
            defaults = get_func_optional_params(func)
            func_required_params = func_params[: len(func_params) - len(defaults)][
                : len(args) + len(kwargs)
            ]
            # All the rest should be fixtures or will raise an error because tey are not provided
            argnames = (*func_required_params, *defaults.keys())

            func.__defaults__ = None

        case_argvalues = []
        for arg_index, argname in enumerate(argnames):
            new_argvalue = None

            if argname in defaults:
                # Has default value - use it
                new_argvalue = defaults[argname]

            if arg_index < len(args):
                # Provided in args - use it
                new_argvalue = args[arg_index]

            if argname in kwargs:
                # Provided in kwargs - use it
                new_argvalue = kwargs[argname]

            case_argvalues.append(new_argvalue)

        case_param_sets = [pytest.param(*case_argvalues, marks=marks, id=name)] + case_param_sets

        return wrap_func(
            argnames=argnames,
            param_sets=case_param_sets,
            defaults=defaults,
        )(func)

    return decorator