Any markers and ellipses

Let’s look at how the Ellipsis marker (...) works in mockito.

Assume:

class C:
    def function(self, one, two):
        ...

Given

when(C).function(...)

The sole ... denotes a “whatever” matcher.

These are allowed:

function(1, 2)
function("1", 2)

But the real function signature still applies. So for function(one, two), these raise:

function()         # raises
function(1, 2, 3)  # raises

When configured as:

when(C).function(2, ...)

The trailing ... denotes a rest matcher. We match up to the 2; the rest is accepted.

function(2, 2)
function(2, "22")
function(2, True)

function(2, 3, 4) still raises for fixed arity signatures.

The rest matcher also works with variadics:

def function(one, *args, **kwargs): ...

when(C).function(1, ...)

Allows:

function(1)
function(1, 2)
function(1, 2, 3)
function(1, 2, three=3)

Relation to any

... can also be used in a fixed position as an ad-hoc any matcher.

Assume:

def fetch(location, retry=5, **options): ...

Then:

when(C).fetch("https://example.com/", retry=...)

means retry must be present, but its value is ignored.

fetch("https://example.com/", retry=2)
fetch("https://example.com/", retry=5)

Both are allowed. These raise:

fetch("https://example.com/")
fetch("https://example.com/", headers={})

So: in a fixed position consumes exactly one value (equivalent to any), and only a trailing positional acts as a rest matcher.

E.g.

when(C).fetch(..., ..., headers=...)
when(C).fetch(any, any, headers=any)

are equivalent and allow:

fetch("https://foobar.com/", 2, headers={})

Note

fetch("https://foobar.com/", 2, headers={}) and fetch("https://foobar.com/", retry=2, headers={}) are not the same invocations in mockito, even if the function signature would allow both variants (i.e. when fetch is not defined as def fetch(location, *, retry=5, **options): ...).

You are responsible for configuring the style you expect your code to use. If your codebase mixes both styles, configure both variants:

when(C).fetch(..., ..., headers=...)
when(C).fetch(..., retry=..., headers=...)

We used the built-in Python any here as marker. That is easy because you don’t have to import anything, just like with .... However, you can also import the “real” any marker:

from mockito import any
from mockito.matchers import any, any_, ANY

We have various spellings for the marker. Choose whatever fits your mood. This marker also consumes one argument at a time but allows constraints:

when(C).fetch("https://example.com/", retry=any(int))

With that configuration, naturally follows:

fetch("https://example.com/", retry=3)    # passes
fetch("https://example.com/", retry="3")  # raises

Relation to *args

If you want to match *args (multiple arguments), use args:

def sum(*args): ...

from mockito import args
when(C).sum(1, 2, *args)

Allows:

sum(1, 2, 3)
sum(1, 2, 3, 4)

That is similar to plain trailing ..., but args also composes with keyword arguments.

Assume:

def sum(*args, init=0): ...

from mockito import args
when(C).sum(1, 2, *args, init=5)

Allows:

sum(1, 2, 3, init=5)
sum(1, 2, 3, 4, init=5)

But:

when(C).sum(1, 2, ..., init=5)

uses fixed-position ... (one value), so it allows:

sum(1, 2, 3, init=5)

and disallows:

sum(1, 2, init=5)
sum(1, 2, 3, 4, init=5)

Relation to **kwargs

Ideally we could write:

when(C).fetch("https://example.com/", retry=..., ...)

but that’s not valid Python syntax. Use kwargs instead:

from mockito import kwargs
when(C).fetch("https://example.com/", retry=..., **kwargs)

Allows:

fetch("https://example.com/", retry=2, headers={})

And:

from mockito import kwargs
when(C).fetch(..., retry=2, **kwargs)

Allows:

fetch("https://example.com/", retry=2)
fetch("https://foobar.com/", retry=2)
fetch("https://foobar.com/", retry=2, headers={})

Use kwargs as the rest marker where ... is not syntactically available because specific keyword arguments are already configured.