Handy pytest snippets

I seem to spend 90% of my time these days writing Python tests. Which one could argue is how it should be, but whatever.

Some handy Pytest snippets I use all the time




Basic monkeypatch fixture

Monkeypatching is the thing I do most of the time. It's basically a mechanism you can use to bypass a function and replace it with a custom test function of your own.

For example, you may be doing something in your real code that calls a database to get some data. You don't want your tests to do this though, that would mean managing a test database somewhere and that sounds like a lot of work (it is, trust me I have done it and it is terrible).

So you make sure the thing that gets the data is in a nice function all of its own - which it will be already right because single responsibility, DRY, etc etc...? - then replace it with a fake one.

This is a stupidly simple example, because why not. I have a function that generates a 12 char code, checks it is unique in the database then returns the new unique code. Yeh the code is rubbish, but that's not the point. The test is the point :D

Fixture (in conftest.py)

@pytest.fixture()
def mock_unique_code(monkeypatch):    
    monkeypatch.setattr(code_generator, "check_code_unique", True)

Test code (in test_code_generator.py)

def test_generate_code(mock_unique_code):
    new_code = generate_code()
    assert len(new_code) == 12

Actual code (in code_generator.py)

def generate_code():

    unique = False

    while unique is not True:
        new_code = "".join(secrets.choice(string.ascii_characters) for i in range(0, 12))
    return new_code


def check_code_unique(code):
    return True

OK so I haven't actually written the check_code_unique function yet. TDD amirite? Doesn't matter though, it's the generate_code function we're testing so I've monkeypatched the check_code_unique in the fixture so it will always return True.

The basic format is to create a function that returns what you need for the tests, then replace the real function with your fake test one like this: monkeypatch.setattr([YOUR MODULE], "[YOUR FUNCTION]", [REPLACEMENT FUNCTION]). Simple right?




Basic monkeypatch fixture with parameters

The example above is fine if you always want generate_code to return True. But wouldn't it be handy if you also tested what happened if it returned False? Of course it would, you silly sausage.

This is also super simple, but it looks fancier so will make you feel like a cooler dev, cos lambda functions are cool.

The actual code and test code are the same, we just need to change the fixture to do this:

Fixture (in conftest.py)

@pytest.fixture(params=[True, False])
def mock_unique_code(monkeypatch, request):
    monkeypatch.setattr(
        code_generator,
        "check_code_unique",
        lambda r: request.param
    )

Same as before except we are setting some params in the fixture and our replacement function is a bit fancier. Whatever you put in params will run as a separate test every time the fixture is used. This is just a bool so it is simple, but you can put a list of any old stuff in here and it will run a test case for each item in the list. Awesome huh.

The current item from the param list is accessed via request.param. Lambda functions look cool and complex, but really they are just tiny functions that just do one thing. TBH a lambda is not really necessary here, you could just return request.param but just in case you need to return more items, or you need to manipulate it on some way, you can do that in here too.