cat /dev/brain

Betamax 0.5.0 Now With A PyTest Fixture

Tonight marks the release of Betamax 0.5.0. Beyond just reworking the documentation, it also marks the addition of two integrations with popular testing frameworks. Betamax now ships with a pytest fixture that provides an instantiated Session that is being recorded by Betamax. Let's look at how we might use it.

How Can I Use This Now?

First, we should obviously have both betamax and pip installed:

pip install betamax>=0.5.0 pytest

We now have 3 options to enable the test fixture:

  1. Globally for all tests
  2. Locally for a specific module
  3. On the specific class or test we want to use the fixture

To enable the fixture globally, we need only place

# conftest.py

import pytest

pytest.mark.usefixtures('betamax_session')

In conftest.py for our testsuite.

Similarly, for a specific module, we need only add

import pytest

pytest.mark.usefixtures('betamax_session')

To the top of the module.

Finally, to add this to a class or method, you can use pytest.mark.usefixtures as a decorator, e.g.,

import pytest


@pytest.mark.usefixtures('betamax_session')
def test_something(betamax_session):
    pass


@pytest.mark.usefixtures('betamax_session')
def TestSomethingClass(object):
    def test_something_on_class(betamax_session):
        pass

    def test_something_else_on_class(betamax_session):
        pass

Of course, you can use the betamax_session fixture anywhere where fixtures are accepted (e.g., as parameters to other fixtures). So let's say you have something that manages your Session object for which you can provide an already instantiated session, if you're testing that object, you might do something like:

# Disclaimer: I didn't test this example, it should be a close
# approximation to something that works though.
@pytest.fixture
@pytest.mark.usefixtures('betamax_session')
def client(betamax_session):
    return MyClient(session=betamax_session)

def test_get_user(client):
    client.get_user(username='example')

This should automatically generate a cassette name for you and record the interactions provided you've configured Betamax correctly.

Is This Even Tested?

Yes it is! We have both unit and integration tests written for the pytest fixture. If you examine them closely, you'll probably note something interesting about the integration tests. Specifically you might notice that we make an assertion during finalization. The way Betamax and the pytest fixture work together, the cassette won't exist on disk until after the test has finished. This means that if we want to ensure that the cassette was recorded for the test, we have to make the assertion after the test. It's not exactly great, but it works. The unit tests, however, take the approach of mocking out different aspects of what the fixture needs.

Testing the fixture was a bit tricky. First, the fixture itself is a total of 33 lines, 16 of which are the docstring. (So just over half of the code, counting the decorator from pytest, the function definition, and whitespace actually does anything.) 8 of the 17 lines of code generate the cassette name.

So what is there to test? Well, in our unit tests we test the cassette name generation; we test that we use the recorder as expected; and we test that we add a finalizer to stop recording the interactions on the session. Finally, our integration test catches the most critical part of the functionality: that the entry point for pytest is correct and works. There isn't a whole lot of code to tests so our tests are rather thin.