Writing Tests#

This section contains a series of guides and guidelines for writing tests in the conda repository.


Guides#

Integration Tests This guide gives an overview of how to write integration tests using full command invocation. It also covers creating fixtures to use with these types of tests.


General Guidelines#

Note

It should be noted that existing tests may deviate from these guidelines, and that is okay. These guidelines are here to inform how we would like all new tests to look and function.

Preferred test style (pytest)#

Although our codebase includes class-based unittest tests, our preferred format for all new tests are pytest style tests. These tests are written using functions and handle the setup and teardown of context for tests using fixtures. We recommend familiarizing yourself with pytest first before attempting to write tests for conda. Head over to their Getting Started Guide to learn more.

Organizing tests#

Tests should be organized in a way that mirrors the main conda module. For example, if you were writing a test for a function in conda/base/context.py, you would place this test in tests/base/test_context.py.

The "conda.testing" module#

This is a module that contains anything that could possibly help with writing tests, including fixtures, functions, and classes. Feel free to make additions to this module as you see fit, but be mindful of organization. For example, if your testing utilities are primarily only for the base module considering storing these in conda.testing.base.

Adding new fixtures#

For fixtures that have a very limited scope or purpose, it is okay to define these alongside the tests themselves. However, if these fixtures could be used across multiple tests, then they should be saved in a separate fixtures.py file. The conda.testing module already contains several of these files.

If you want to add new fixtures within a new file, be sure to add a reference to this module in tests/conftest.py::pytest_plugins. This is our preferred way of making fixtures available to our tests. Because of the way these are included in the environment, you should be mindful of naming schemes and choose ones that likely will not collide with each other. Consider using a prefix to achieve this.

The context object#

The context object in conda is used as a singleton. This means that everytime the conda command runs, only a single object is instantiated. This makes sense as it holds all the configuration for the program and re-instantiating it or making multiple copies would be inefficient.

Where this causes problems is during tests where you may want to run conda commands potentially hundreds of times within the same process. Therefore, it is important to always reset this object to a fresh state when writing tests.

This can be accomplished by using the reset_context function, which also lives in the conda.base.context module. The following example shows how you would modify the context object and then reset it using the reset_conda_context pytest fixture:

import os
import tempfile

from conda.base.context import reset_context, context
from conda.testing.fixtures import reset_conda_context

TEST_CONDARC = """
channels:
  - test-channel
"""


def test_that_uses_context(reset_conda_context):
    # We first created a temporary file to hold our test configuration
    with tempfile.TemporaryDirectory() as tempdir:
        condarc_file = os.path.join(tempdir, "condarc")

        with open(condarc_file, "w") as tmp_file:
            tmp_file.write(TEST_CONDARC)

        # We use the reset_context function to load our new configuration
        reset_context(search_path=(condarc_file,))

        # Run various test assertions, below is an example
        assert "test-channel" in context.channels

Using this testing fixture ensures that your context object is returned to the way it was before the test. For this specific test, it means that the channels setting will be returned to its default configuration. If you ever need to manually reset the context during a test, you can do so by manually invoking the reset_context command like in the following example:

from conda.base.context import reset_context


def test_updating_context_manually():
    # Load some custom variables into context here like above...

    reset_context()

    # Continue testing with a fresh context...