06 PyTest Mocking
Mocking¶
In pytest
, mocking
is a technique used to isolate test environments by replacing complex, unpredictable, or external system dependencies with objects that mimic the behavior of these dependencies in a controlled way.
When We Need Mocking
Mocking
is particularly useful in the following scenarios:
- External Dependencies: When your code interacts with external systems like
web APIs
,databases
, orexternal libraries
, mocking these can help you test your code without the overhead or unpredictability of these systems. - Complex Internal Dependencies: For complex modules or classes that your code depends on, mocking can help you simplify these dependencies to test specific functionality without the complexity.
- Unpredictable Outcomes: When dealing with dependencies that can produce unpredictable results (e.g., current date, random numbers), mocking can be used to produce predictable and testable outputs.
- Side Effects: If a function or method has side effects like modifying a file or database, mocking can be used to simulate these side effects without making actual changes.
Example with database¶
- In
service.py
, construct a function to get user from db
database = {
1: "Alice",
2: "Bob",
3: "Charlie"
}
def get_user_from_db(user_id):
return database.get(user_id)
- Mock
get_user_from_db
import service
import pytest
import unittest.mock as mock
@mock.patch("service.get_user_from_db")
def test_get_user_from_db(mock_get_user_from_db):
mock_get_user_from_db.return_value = "Mocked Alice"
user_name = service.get_user_from_db(1)
assert user_name == "Mocked Alice"
@mock.patch("service.get_user_from_db")
: This is a decorator from the mock module used to replace theget_user_from_db
function in the service module with a mock object during this test.- The original function is restored after the test completes. The mock object is passed as an argument to the test function, named
mock_get_user_from_db
here.
- The original function is restored after the test completes. The mock object is passed as an argument to the test function, named
Inside the function,
mock_get_user_from_db.return_value = "Mocked Alice"
sets the return value of the mock object.- When the
get_user_from_db
function is called within this test, it will return "Mocked Alice"
instead of executing its real logic. user_name = service.get_user_from_db(1)
: Calls the mockedget_user_from_db
function with an argument (1), which would typically represent a user ID. Due to mocking, the function returns"Mocked Alice"
.
- When the
assert
user_name == "Mocked_Alice"
: This assertion checks that the user_name variable equals"Mocked_Alice"
. However, there's a typo here; the expected string should be"Mocked Alice"
(with a space, not an underscore), matching the return_value set on the mock object.
Example with API¶
Write the
get_user
function to be testdef get_users(): response = requests.get("https://jsonplaceholder.typicode.com/users") if response.status_code == 200: return response.json() raise requests.HTTPError
Mock
requests.get
since we do not want to get data from online in this test@mock.patch("requests.get") def test_get_user(mock_get): mock_response = mock.Mock() mock_response.status_code = 200 mock_response.json.return_value = {"id":1,"name":"Eric Yang"} mock_get.return_value = mock_response data = service.get_users() assert data == {"id":1,"name":"Eric Yang"} @mock.patch("requests.get") def test_get_user_httperror(mock_get): mock_response = mock.Mock() mock_response.status_code = 404 mock_get.return_value = mock_response with pytest.raises(requests.HTTPError): service.get_users()