Hone logo
Hone
Problems

Mocking a User Authentication Service

In software development, particularly in testing, we often need to isolate the component we're testing from its dependencies. Mocking allows us to create "fake" versions of these dependencies that we can control. This challenge focuses on creating a mock object for a user authentication service, a common scenario where isolation is crucial for robust unit testing.

Problem Description

Your task is to create a Python class that acts as a mock for a UserAuthenticationService. This mock service should simulate the behavior of a real authentication service but allow us to specify its responses during tests. Specifically, the mock service should have a method authenticate_user(username, password) that, when called, returns a dictionary indicating success or failure.

Key Requirements:

  1. MockUserAuthenticationService Class: Create a class named MockUserAuthenticationService.
  2. __init__ Method: The constructor should accept an optional argument expected_calls. This argument will be a list of tuples, where each tuple represents a call to authenticate_user and the corresponding return value.
    • Example: [("testuser", "password123", {"authenticated": True, "user_id": 101}), ("admin", "wrongpass", {"authenticated": False})]
    • If expected_calls is not provided, the mock should have no predefined responses.
  3. authenticate_user(username, password) Method: This method should:
    • If expected_calls was provided during initialization, it should try to find a matching call in expected_calls based on username and password.
    • If a match is found, it should return the predefined return value for that call.
    • If no exact match is found, it should return {"authenticated": False, "error": "No matching mock call found"}.
    • It should keep track of the calls made to it internally (though not explicitly required for the return value, it's good practice for more advanced mocking).
  4. Verification (Optional but Recommended for real-world use): Although not strictly required by the return value, a real mock object would often have a way to verify that it was called as expected. For this challenge, you can optionally add a method assert_calls_made() that checks if all predefined expected_calls were consumed.

Examples

Example 1:

# Mock setup
mock_auth_service = MockUserAuthenticationService(expected_calls=[
    ("alice", "pass123", {"authenticated": True, "user_id": 42}),
    ("bob", "wrongpass", {"authenticated": False})
])

# Test cases
result1 = mock_auth_service.authenticate_user("alice", "pass123")
print(result1)

result2 = mock_auth_service.authenticate_user("bob", "wrongpass")
print(result2)

result3 = mock_auth_service.authenticate_user("charlie", "anypass")
print(result3)
Output:
{'authenticated': True, 'user_id': 42}
{'authenticated': False}
{'authenticated': False, 'error': 'No matching mock call found'}

Explanation: The first call matches the first entry in expected_calls, returning {"authenticated": True, "user_id": 42}. The second call matches the second entry, returning {"authenticated": False}. The third call doesn't match any predefined call, so it returns the default error response.

Example 2:

# Mock setup with no predefined calls
mock_auth_service = MockUserAuthenticationService()

# Test case
result = mock_auth_service.authenticate_user("guest", "guest")
print(result)
Output:
{'authenticated': False, 'error': 'No matching mock call found'}

Explanation: Since no expected_calls were provided, any call to authenticate_user will result in the default "no matching mock call found" response.

Example 3: (Illustrating a potential verification scenario)

# Mock setup
mock_auth_service = MockUserAuthenticationService(expected_calls=[
    ("admin", "secure", {"authenticated": True, "user_id": 1})
])

# Simulate a call
mock_auth_service.authenticate_user("admin", "secure")

# In a real test, you might then call something like this:
# mock_auth_service.assert_calls_made() # This would pass if the above call was made.
# If you tried to call it again without making another corresponding call, it might raise an error.

Constraints

  • The username and password arguments to authenticate_user will always be strings.
  • The return values in expected_calls can be any valid Python dictionary.
  • The expected_calls list will contain tuples of (username, password, return_value).
  • For simplicity, assume that identical (username, password) pairs in expected_calls will be handled by returning the first match found.

Notes

  • Think about how to efficiently search through the expected_calls list.
  • Consider what happens if the same (username, password) pair is expected multiple times in expected_calls. The current requirement is to return the first match. In more complex mocking scenarios, you might want to track how many times each specific call was expected and made.
  • The primary goal is to create a flexible mock that can be configured to return specific results for specific inputs, enabling isolated unit testing.
Loading editor...
python