13

I want to test function calls with optional arguments.

Here is my code:

list_get()
list_get(key, "city", 0)
list_get(key, 'contact_no', 2, {}, policy)
list_get(key, "contact_no", 0)
list_get(key, "contact_no", 1, {}, policy, "")
list_get(key, "contact_no", 0, 888)

I am not able to parametrize it due to optional arguments, so I have written separate test functions for each api call in pytest.
I believe there should be better way of testing this one.

Forge
  • 6,538
  • 6
  • 44
  • 64
Pavan Gupta
  • 17,663
  • 4
  • 22
  • 29

4 Answers4

15

You may be able to use the * operator:

@pytest.mark.parametrize('args,expected', [
    ([], expVal0),
    ([key, "city", 0], expVal1),
    ([key, 'contact_no', 2, {}, policy], expVal2)
    ([key, "contact_no", 0], expVal3)
    ([key, "contact_no", 1, {}, policy, ""], expVal4)
    ([key, "contact_no", 0, 888], expVal5)
])
def test_list_get(args, expected):
    assert list_get(*args) == expected
timmow
  • 3,595
  • 2
  • 23
  • 22
Ezequiel Muns
  • 7,492
  • 33
  • 57
4

In addition to the answers @forge and @ezequiel-muns I suggest using some sugar from pyhamcrest:

import pytest
from hamcrest import assert_that, calling, is_not, raises

@pytest.mark.parametrize('func, args, kwargs', [
    [list_get, (), {}],
    [list_get, (key, "city", 0), {}],
    [list_get, (key, "contact_no", 1, {}, policy, ""), {}],
    [list_get, (), {'key': key}],
])
def test_func_dont_raises(func, args, kwargs):
    assert_that(calling(func).with_args(*args, **kwargs), is_not(raises(Exception)))
Sergei Voronezhskii
  • 2,184
  • 19
  • 32
1

For future readers who come to this question trying to set up @parameterized tests to generate a Cartesian set of parameters AND sometimes do not want to pass a given parameter at all (if optional), then using a filter on None values will help

def function_under_test(foo="foo-default", bar="bar-default"):
    print([locals()[arg] for arg in inspect.getargspec(function_under_test).args])


@pytest.mark.parametrize("foo", [None, 1, 2])
@pytest.mark.parametrize("bar", [None, "a", "b"])
def test_optional_params(foo, bar):
    args = locals()
    filtered = {k: v for k, v in args.items() if v is not None}
    function_under_test(**filtered)  # <-- Notice the double star

Sample run:

PASSED   [ 11%]['foo-default', 'bar-default']
PASSED   [ 22%][1, 'bar-default']
PASSED   [ 33%][2, 'bar-default']
PASSED   [ 44%]['foo-default', 'a']
PASSED   [ 55%][1, 'a']
PASSED   [ 66%][2, 'a']
PASSED   [ 77%]['foo-default', 'b']
PASSED   [ 88%][1, 'b']
PASSED   [100%][2, 'b']
Drakes
  • 23,254
  • 3
  • 51
  • 94
0

I think what you are doing is good, but I would suggest to pass key-value arguments so you won't get the arguments order mixed up.

I assume your function header looks something like this:

def list_get(key=None, city=None, contact_no=None, policy=None):
    ...

In your test define the list of arguments combination you would like to test:

kwargs = {'key': '..', 'city': '..', 'contact_no': '..', 'policy': '..'} 
list_get(**kwargs) 

kwargs_all_default_values = {}
list_get(**kwargs_all_default_values)
Forge
  • 6,538
  • 6
  • 44
  • 64