Skip to main content

A small library for loading unittest fixtures

Project description

unittest-fixtures: A small library for loading unittest fixtures

Description

unittest-fixtures spun off from my Gentoo Build Publisher project. I use unittest, the test framework in the Python standard library, where it's customary to define a TestCase's fixtures in the .setUp() method. Having done it this way for years, it occurred to me one day that this goes against OCP. What if instead of cracking open the .setUp() method to add a fixture to a TestCase one could instead add a decorator? That's what unittest-fixtures allows one to do.

from unittest_fixtures import given

@given("dog")
class MyTest(TestCase):
    def test_method(self, fixtures):
        dog = fixtures.dog

In the above example, "dog" is a fixture function. Fixture functions are passed to the given decorator. They can be passed as a string or reference. If one is passed as a string, that fixture function is looked for in a standard place. By default this is the tests/fixtures.py module in one's project's root.

Fixture functions

Fixture functions are functions that one defines that return a "fixture". For example the above dog fixture might look like this:

from unittest_fixtures import fixture

@fixture()
def dog(fixtures):
    return Dog(name="Fido")

Fixture functions are always passed a Fixtures argument. Because fixtures can depend on other fixtures. For example:

@fixture("dog")
def person(fixtures):
    p = Person(name="Jane")
    p.pet = fixtures.dog
    return p

Fixture functions can have keyword parameters, but those parameters must have defaults.

@fixture
def dog(fixtures, name="Fido"):
    return Dog(name=name)

Then one's TestCase can use the where decorator to passed the parameter:

from unittest_fixtures import given, where

@given("dog")
@where(dog__name="Buddy")
class MyTest(TestCase):
    def test_method(self, fixtures):
        dog = fixtures.dog
        self.assertEqual("Buddy", dog.name)

Duplicating fixtures

The unittest-fixtures library allows one to use a fixture more than once. This can be done by passing the fixture as a keyword argument giving different names to the same fixture. Different parameters can be passed to them:

@given(fido="dog", buddy="dog")
@where(fido__name="Fido", buddy__name="Buddy")
class MyTest(TestCase):
    def test_method(self, fixtures):
        self.assertEqual("Buddy", fixtures.buddy.name)
        self.assertEqual("Fido", fixtures.fido.name)

Fixture-depending fixtures will all use the same fixture, but only if they have the same name. So in the above example, if we also gave the TestCase the person fixture, that person would have a different dog because it depends on a fixture called "dog". However this will work:

@given("dog", "person")
class MyTest(TestCase):
    def test_method(self, fixtures):
        dog = fixtures.dog
        person = fixtures.person
        self.assertIs(person.pet, dog)

@where (fixture parameters)

The where decorator can be used to pass parameters to a fixture function. Fixture functions are not required to take arguments. To pass a parameter to a function, for example pass name to the dog fixture it's the name of the function, followed by __ followed by the parameter name. For example: dog__name. Fixture functions can also have a parameter that is the same name as the fixture itself. For example:

@given("settings")
@where(settings={"DEBUG": True, "SECRET": "sauce"})
class MyTest(TestCase):
    ...

Fixtures as context managers

Sometimes a fixture will need a setup and teardown process. If unittest-fixtures is supposed to remove the need to open setUp(), then it must also remove the need to open tearDown(). And it does this by by defining itself as a generator function. For example:

import tempfile

@fixture()
def tmpdir(fixtures):
    with tempfile.TemporaryDirectory() as tempdir:
        yield tempdir

Using the unittest.mock library is another good example of using context manager fixtures.

fixture-depending fixtures

As stated above, fixtures can depend on other fitures. This is done by "declaring" the dependencies in the fixture decorator. Fixtures are then passed as an argument to the fixture function:

@fixture("settings", "tmpdir")
def jenkins(fixtures, root=None):
    root = root or fixtures.tmpdir
    settings = replace(fixtures.settings, STORAGE_PATH=root)
    return Jenkins.from_settings(settings)

The above example shows that one can get pretty fancy... or creative with one's fixture definitions.

Fixtures can also have named dependencies. So in the above example, if one wanted a different tmpdir than the "global" one:

@fixture("settings", jenkins_root="tmpdir")
def jenkins(fixtures, root=None):
    root = root or fixtures.jenkins_root
    settings = replace(fixtures.settings, STORAGE_PATH=root)
    return Jenkins.from_settings(settings)

If a TestCase used both jenkins and tmpdir:

@given("tmpdir", "jenkins")
class MyTest(TestCase):
   def test_something(self, fixtures):
       self.assertNotEqual(fixtures.jenkins.root, fixtures.tmpdir)

Again if the two fixtures have different names then they are two separate fixtures. In general one should not use named fixtures unless one wants multiple fixtures of the same type.

@parametrized

Not so much a fixtures tool, however unittest-fixtures also comes with a @parametrized decorator that acts as a wrapper for unittest's subtests. A rather contrived example comes from unittest-fixtures own tests:

from unittest_fixtures import parametrized

class ParametrizeTests(TestCase):
    values = {1, 2}

    @parametrized([[1, values], [2, values], [None, values]])
    def test(self, value, values):
        if value is not None:
            self.assertIn(value, values)
            values.discard(value)
            return
        self.assertEqual(set(), values)

Conclusion

Creating unittest fixtures is clean and fun with unittest-fixtures.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unittest_fixtures-1.0.0.tar.gz (10.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unittest_fixtures-1.0.0-py3-none-any.whl (8.1 kB view details)

Uploaded Python 3

File details

Details for the file unittest_fixtures-1.0.0.tar.gz.

File metadata

  • Download URL: unittest_fixtures-1.0.0.tar.gz
  • Upload date:
  • Size: 10.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for unittest_fixtures-1.0.0.tar.gz
Algorithm Hash digest
SHA256 361d6d4d5faa5b3951574560f51ff1201caf393d92191f8ef202a590a7ccc897
MD5 7891f0ae228613d3d30d9452493487ca
BLAKE2b-256 df6ee32af8532cbbf02fe94509297010e17ceb024a3970bdb1cb552afdc64de7

See more details on using hashes here.

File details

Details for the file unittest_fixtures-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for unittest_fixtures-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3001fdc9f9354537208dbf5f0655198fa838c513f99d84b448117b4eec247e40
MD5 66b0e5a8265e585e6b2d90911fedb520
BLAKE2b-256 83d97557262cf7c00c2a064f56c37d4e8d8b0eb3f5f696fba8995580d41fe130

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page