3 Simple Ways To Define Your Pytest Environment Variables With Examples

When writing Python code, you’ve probably used Environment Variables, at some point.

Whether in serverless lambda functions or stateful deployed apps.

Environment Variables are variables whose state is passed to the code or script at runtime.

Or variables whose state doesn’t change for execution (e.g. related to the system, user or network).

Although the best practices point to maintaining stateless code at all times, this is often not always possible.

Imagine deploying your code to a server and finding out it doesn’t work.

So how do you ensure that Environment Variables are correctly picked up by your code at run time?

Pytest environment variable tests help you test your code in a TDD (test-driven development) way using Pytest, a Unit Testing Wrapper build over theunittest framework.

It provides an excellent interface for writing high-coverage test code.

In this article, we’ll look at a few ways to set and test environment variables using Pytest. Namely

  • How To Set Environment Variables In Pytest
    • 1. Use The pytest-env Package (TL;DR)
    • 2. Define Env Vars In The Test
    • 3. Use Fixtures & Monkeypatch
    • Other Ways (CLI)

This will ensure your code efficiently handles environment variables and raises appropriate errors if these variables are not defined.

Let’s get started, shall we?

Link To GitHub Repo

How To Set Environment Variables In Pytest

Let’s look at a few ways to set Environment Variables in your Unit Tests.

1. Use The pytest-env Package (TL;DR)

If you’re here for a quick answer, here it is.

You can use the pytest-env package and a pytest.ini file to define Environment Variables your code needs at test run-time. If you’re not familiar with pytest-ini files please check out this article for a brief practical overview.

Let’s take a look at the source code.

core.py (Source Code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
from dotenv import load_dotenv

load_dotenv()


def load_env_vars() -> str:
"""
Function to load and print environment variables
:return: String containing env vars
"""
account_id = os.getenv("ACCOUNT_ID")
api_endpoint = os.getenv("API_ENDPOINT")
deployment_stage = os.getenv("DEPLOYMENT_STAGE")
return f"The API endpoint `{api_endpoint}` has been deployed to " \
f"`{deployment_stage}` in Account # `{account_id}`"

The source code contains a very simple method load_env_vars that loads 3 environment variables

  1. ACCOUNT_ID
  2. API_ENDPOINT
  3. DEPLOYMENT_STAGE

from a .env file using the python-dotenv package. This package helps us define environment variables in a .env file and easily load them into our source code.

Next, if you navigate into the /tests/ folder you’ll see 3 unit tests, each using a different form of loading environment variables, along with a pytest.ini file.

Important Note: Environment Variables defined in your pytest.ini file take precedence over a .env file or local environment during the test execution.

pytest.ini

1
2
3
4
5
[pytest]
env =
ACCOUNT_ID=9876
DEPLOYMENT_STAGE=dev2
API_ENDPOINT=www.test_api_endpoint.com

test_env_variables_examples_pytest_env.py

1
2
3
4
5
6
7
8
9
def test_load_env_vars_using_pytest_env() -> None:
"""
Using pytest_env and pytest.ini
:return: None
"""
expected_response = "The API endpoint `www.test_api_endpoint.com` " \
"has been deployed to `dev2` in Account # `9876`"
actual_response = load_env_vars()
assert actual_response == expected_response

pytest_environment_variables_pytest_env

In the above example, you can see how Pytest has automatically detected the 3 environment variables from our pytest.ini file.

Which means no overriding, no mocking or patching, and no fixtures.

IMO, this is the simplest and a fairly reliable way to ensure isolation of the test environment.

Now let’s look at a few other ways to do this.

Note - To run the test_env_variables_examples_basic.py test please comment out the contents in pytest.ini otherwise it will overwrite the environment variables.

2. Define Env Vars In The Test

You can also straightaway define your env variables within the test.

test_env_variables_examples_basic.py

1
2
3
4
5
6
7
8
9
10
11
12
def test_load_env_vars_define_in_test() -> None:
"""
Using os.environ
:return: None
"""
os.environ["DEPLOYMENT_STAGE"] = "dev"
os.environ["API_ENDPOINT"] = "https://api.TEST_URL1.com"
os.environ["ACCOUNT_ID"] = "123"
expected_response = "The API endpoint `https://api.TEST_URL1.com` " \
"has been deployed to `dev` in Account # `123`"
actual_response = load_env_vars()
assert actual_response == expected_response

pytest_environment_variables_basic

In the above code example, the unit test overrides the environment variables within the Unit Test.

While easy to understand, this method is

  • Ugly
  • Not scalable (imagine you had 10–20 env variables)
  • Not reusable (imagine you had 10–20 tests).

Although basic, it’s still a popular way for a few unit tests.

Use Fixtures & Monkeypatch

While this is a popular way of defining environment variables, I’m not a big fan.

Just like the previous method it requires lots of custom fixtures and patching which is fine for small-scale tests but cannot be extended to a huge # of tests.

Let’s look at how it’s done.

test_env_variables_examples_fixtures_monkeypatch.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@pytest.fixture(scope="module")
def deployment_stage_env_var() -> pytest.fixture():
os.environ["DEPLOYMENT_STAGE"] = "staging"
return os.environ["DEPLOYMENT_STAGE"]


@pytest.fixture(scope="module")
def api_endpoint_env_var() -> pytest.fixture():
os.environ["API_ENDPOINT"] = "https://api.TEST_URL2.com"
return os.environ["API_ENDPOINT"]


@pytest.fixture(scope="module")
def account_id_env_var() -> pytest.fixture():
os.environ["ACCOUNT_ID"] = "789"
return os.environ["ACCOUNT_ID"]


def test_load_env_vars_fixtures(deployment_stage_env_var,
api_endpoint_env_var,
account_id_env_var) -> None:
"""
Using Fixtures
:return: None
"""
expected_response = "The API endpoint `https://api.TEST_URL2.com` " \
"has been deployed to `staging` in Account # `789`"
actual_response = load_env_vars()
assert actual_response == expected_response


def test_load_env_vars_monkeypatch(monkeypatch) -> None:
"""
Using Monkeypatch
:return: None
"""
monkeypatch.setenv("DEPLOYMENT_STAGE", "prod")
monkeypatch.setenv("API_ENDPOINT", "https://api.TEST_URL3.com")
monkeypatch.setenv("ACCOUNT_ID", "321")
expected_response = "The API endpoint `https://api.TEST_URL3.com` " \
"has been deployed to `prod` in Account # `321`"
actual_response = load_env_vars()
assert actual_response == expected_response

pytest_environment_variables_fixtures_monkeypatch

In the above code, you can see we’ve defined a fixture for each of the environment variables.

You can also define the fixtures in Pytest Conftest as a best practice.

In the test test_load_env_vars_fixtures we pass these fixtures as parameters and verify the asserted response.

The other test test_load_env_vars_monkeypatch uses the monkeypatch.setenv attribute to set each of the env variables.

Here’s a quick refresher on Pytest Monkeypatch.

While effective and convenient this may not be the cleanest, most scalable of approaches. Although it may work for some use cases.

Other Ways

Some other ways you can pass environment variables to your Pytest unit tests include via the CLI (one we didn’t talk about).

Conclusion

In this article, you’ve seen a few ways to define environment variables for your Unit Tests.

We looked at examples using the python-dotenv library, defining env vars in the test, fixtures and monkeypatching.

While there is no one size fits all solution, my goal here was to present a few ways, so you can choose the one that best suits your use case.

For a personal side project, it probably doesn’t matter. But if you’re writing enterprise-level code, you must be mindful of security and best practices.

I hope you found this helpful. Any questions please hit me up.

Till the next time… Cheers!

Additional Reading

Working with Environment Variables in Python

Environment Variables: What They Are and How To Use Them