-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unit Testing Flask Route Handlers #7
Comments
Hi, thanks for the nice words. I do agree that at the moment testing autowire targets can be a bit difficult. The idea of temporarily overriding dependencies with custom implementations also sounds good. When autowiring, the container always refers to I hope this helps resolving your issue until the feature is (hopefully soon) supported! Example: # Set up: our service and a view
@container.register
class GreeterService:
def greet(self, name) -> str:
return f"Hi {name}"
@app.get("/greet")
@container.autowire
def home(greeter: GreeterService) -> str:
name = request.args.get("name")
return greeter.greet(name)
# In your tests
class GreetViewTest(unittest.TestCase):
def setUp(self) -> None:
self.client = create_app().test_client()
def test_mock_autowired(self):
res = self.client.get("/greet?name=wireup")
self.assertEqual("Hi wireup", res.text)
# Create a mock for the "greet" method.
mock = MagicMock()
mock.greet = MagicMock(return_value="It's mocked!")
# Set the initialized object to the one you want to override
# The key is a tuple consisting of the class and the qualifier.
# which is by default to None unless you set something using
# qualifier="..." during registration.
container._DependencyContainer__initialized_objects[(GreeterService, None)] = mock
res = self.client.get("/greet?name=wireup")
self.assertEqual("It's mocked!", res.text) We can also create a context manager for this. This is something hacky I threw together you can use to unblock yourself: class ContainerOverride:
def __init__(
self,
dependency_container: DependencyContainer,
override: type,
new_value: Any,
qualifier: ContainerProxyQualifierValue = None,
):
self.container = dependency_container
self.override = override
self.new_value = new_value
self.qualifier = qualifier
self.existing_instance = dependency_container._DependencyContainer__initialized_objects.get((override, qualifier)) # noqa
def set_value(self, value: Any) -> None:
self.container._DependencyContainer__initialized_objects[(self.override, self.qualifier)] = value # noqa
def __enter__(self):
self.set_value(self.new_value)
def __exit__(self, exc_type, exc_val, exc_tb):
if self.existing_instance:
self.set_value(self.existing_instance)
else:
del self.container._DependencyContainer__initialized_objects[(self.override, self.qualifier)]
return True Which we can use as follows with ContainerOverride(container, override=GreeterService, new_value=greeter_mock):
res = self.client.get("/greet?name=wireup")
self.assertEqual("It's mocked!", res.text) Adjust as necessary to be able to mock multiple things simoultaneously. |
This worked perfectly. 💯 Thanks so much! After running with the above hacky solution for a wee while, I'm happy to contribute this back if we tinker with it. I'll put it in a gist so versioning can be seen. |
This Gist will be where I put any changes to |
#13 is a draft pr implementing this. Any feedback from your usage so far is welcome. |
Hiya! Greatly enjoying this lib; we are exploring DI in one of our services and I've got a PR to add wireup and just use the singleton; the only issue the team has run into is we're not able to inject a mock into the flask route handler. Normally, like with services, we just directly use the target object and instantiate it in the unit test with mocks, however this won't work for flask endpoints. Consider the following code
And this unit test with flask's test app mechanism
I've tried setting up multiple containers to have an empty test one and a normal prod one but it has proven... quite difficult to use.
Could we perhaps have a context manager style solution?
The text was updated successfully, but these errors were encountered: