Skip to content

Add change supporting unit testing #537

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

Merged
merged 29 commits into from
May 28, 2025

Conversation

andystaples
Copy link
Collaborator

@andystaples andystaples commented Apr 3, 2025

This PR add support for unit testing Durable client functions, orchestrators, and entities without any change needed to azure-functions-python-library or azure-functions-python-worker.

This is achieved by augmenting the handler returned by the Durable extension into a callable class, with a property storing the original user-defined function.

This way, the worker is still able to process these function types as normal, by passing the context to be parsed by our extension code, but when these new handlers are returned during unit testing, users can access the original function for testing.

Syntactically, here is what that looks like:
Currently, for non-Durable functions and Durable activity functions, even though the Functions decorators replace the original function (see the "Meaning of decorators" section here), users can access the original function either by calling the function directly:

  async def test_function_1(self, client, req):
    function_result = function_1(...)

or by using methods on the wrapped version like so:

  async def test_function_1(self, client, req):
    function_result = function_1.build().get_user_function()(...)

This PR would change the signature of this boiler plate code slightly. We would not preserve the behavior of calling the function directly, and the way to access the original function would look like this:

# Client function test
  async def test_my_client_function(self, client, req):
    function_result = my_client_function.build().get_user_function().client_function(...)

# Orchestrator function test
  async def test_my_orchestrator_function(self, client, req):
    function_result = my_orchestrator_function.build().get_user_function().orchestrator_function(...)

# Entity function test
  async def test_my_entity_function(self, client, req):
    function_result = my_entity_function.build().get_user_function().entity_function(...)

Discussion about whether this solution is adequate and possible improvements is welcome.

@andystaples
Copy link
Collaborator Author

Will fix linting once the implementation is closer to complete

@andystaples andystaples marked this pull request as ready for review April 18, 2025 22:59
@andystaples andystaples requested a review from cgillum as a code owner April 18, 2025 22:59
Copy link
Member

@cgillum cgillum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this approach. Can we add some sample code that shows how to write unit tests (and make sure it works)?

@andystaples andystaples requested a review from nytian as a code owner April 29, 2025 20:55
@andystaples
Copy link
Collaborator Author

I have deliberately excluded the tests from running in the GitHub YAML. The reasons for this are numerous, primarily because the tests are run from the project directory of this project, not from the sample app directories, which leads to two problems:

  1. Relative directory module imports within the sample projects will not work as intended. This can be fixed with the changes from the commit #4584c90 in this PR, but this is not neccesary when users will interact with the samples and leads to cluttered code
  2. The requirements.txt for the Durable project does not contain all the requirements for the test app projects. Primarily, azure-storage-blob is missing. Adding azure-storage-blob to the root-level requirements.txt for this project would likely add an unnecessary package dependency for customers installing this module using pip.

The "correct" way to run the tests from the samples project would be to add a separate GitHub workflow/action to build and test the samples projects independently. I can do this, if there is enough value there, but I have verified that this sample code tests successfully locally when built and run from the sample app directories.

@cgillum
Copy link
Member

cgillum commented Apr 29, 2025

My prayer is that someday I will understand how module imports in Python work. I get horribly confused every time I run into issues like this.

The "correct" way to run the tests from the samples project would be to add a separate GitHub workflow/action to build and test the samples projects independently. I can do this, if there is enough value there

I think it's worth doing. I sometimes like to think that a feature doesn't actually work unless there is a test proving that it works.

@andystaples
Copy link
Collaborator Author

I think it's worth doing. I sometimes like to think that a feature doesn't actually work unless there is a test proving that it works.

Done!

@andystaples andystaples merged commit b0570f0 into dev May 28, 2025
5 checks passed
@andystaples andystaples deleted the andystaples/add-unit-testing-change branch May 28, 2025 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants