Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
296: Pytest Fixtures
Imagine you're a professional chef. Before you start cooking a signature dish—say, a Beef Wellington—you don't just start from scratch the moment the order hits the kitchen. You have a "mise en place." Your onions are already diced, your butter is softened, and your pans are preheated. You've prepared the environment so that the actual act of cooking can be focused and efficient. If you had to chop every single carrot every time you wanted to test a new seasoning, you'd spend more time prepping than actually refining the flavor.
In Pytest, a fixture is exactly that: your mise en place. It's a way to set up the state, data, or connections your tests need before the actual test logic runs, and optionally, clean them up afterward. I've seen too many developers clutter their test functions with ten lines of setup code before they even get to the assert statement. It makes the tests hard to read and a nightmare to maintain. Fixtures pull that noise out of the way.
Stop Repeating Your Setup Logic
Let's say we're building a system that manages user profiles in a database. To test if a user's email can be updated, you first need a user to exist in the database. Without fixtures, you'd be writing the "create user" code in every single test function. It's tedious.
Instead, we define a fixture. You just decorate a function with @pytest.fixture, and then you "inject" that fixture into any test that needs it by adding it as an argument.
import pytest
@pytest.fixture
def mock_user():
# This is our 'mise en place'
return {
"id": 42,
"username": "coding_wizard",
"email": "wizard@example.com",
"is_active": True
}
def test_user_email_update(mock_user):
# The mock_user dict is passed in automatically by pytest
mock_user["email"] = "new_email@example.com"
assert mock_user["email"] == "new_email@example.com"
def test_user_is_active(mock_user):
assert mock_user["is_active"] is True
Notice how test_user_email_update doesn't call mock_user()? Pytest sees the argument name, finds the fixture with the matching name, runs it, and hands the result to your test. It's a clean separation of concerns.
Handling the Cleanup with Yield
Sometimes, preparing the environment isn't enough; you also have to clean up. If you open a database connection or create a temporary file, you can't just leave those hanging around, or you'll eventually crash your test suite or pollute your filesystem. This is where the yield keyword comes in.
When you use yield instead of return, Pytest runs everything before the yield, executes your test, and then comes back to run everything after the yield. I like to think of it as the "cleaning the station" phase of the kitchen analogy.
import pytest
class DatabaseConnection:
def connect(self): print("\nConnecting to DB...")
def close(self): print("Closing DB connection...")
@pytest.fixture
def db_session():
db = DatabaseConnection()
db.connect()
yield db # The test happens right here
db.close() # This runs after the test is finished
def test_database_query(db_session):
print("Running query...")
assert db_session is not None
If the test fails, the code after the yield still runs. This is critical. You don't want a failing test to leave a database lock open that causes the next fifty tests to fail for completely unrelated reasons.
Controlling How Often Setup Happens
By default, a fixture runs once for every single test function that requests it. This is usually what you want because it ensures a "clean slate." However, if your setup is expensive—like spinning up a Docker container or migrating a huge database—doing that for 100 tests will make your suite crawl.
You can change the "scope" of a fixture. For example, scope="module"` tells Pytest to run the fixture once per Python file, and scope="session"` runs it once for the entire test run. Use this sparingly; the more you share state between tests, the higher the risk that one test will accidentally break another.
@pytest.fixture(scope="session")
def global_api_config():
# This only runs once, no matter how many tests use it
return {"api_key": "secret_123", "timeout": 30}
📋 Practical Task
Exercise: Building a Mock API Client Session
You are testing a service that interacts with a remote API. To prevent your tests from making actual network calls, you need a fixture that simulates an API client session. Your goal is to create a fixture that handles both the initialization and the teardown of this session.
Requirements:
- Create a class called
MockApiClientwith two methods:connect()(which should print "API Session Started") anddisconnect()(which should print "API Session Closed"). - Create a pytest fixture named
api_client. - The fixture must use
yieldto ensure thatconnect()is called before the test anddisconnect()is called after the test. - Write a test function called
test_api_connection_existsthat uses theapi_clientfixture and asserts that the client is not None.
Verify your solution by running the test; you should see both the "Started" and "Closed" print statements in the output.
There are no comments for now.