From 1cb001485e75570b849d52cba75e33fd6fe3e5f8 Mon Sep 17 00:00:00 2001 From: Bai Li Date: Wed, 4 Feb 2026 10:16:20 -0800 Subject: [PATCH] feat(Mocking): make it easier to match any input in mockito --- pyproject.toml | 2 +- .../_cli/_evals/mocks/mockito_mocker.py | 26 ++++++---- src/uipath/_cli/_evals/mocks/types.py | 2 +- tests/cli/eval/mocks/test_mocks.py | 52 +++++++++++++++++++ uv.lock | 2 +- 5 files changed, 71 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 16f5c07e0..c62048c86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.7.5" +version = "2.7.6" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/src/uipath/_cli/_evals/mocks/mockito_mocker.py b/src/uipath/_cli/_evals/mocks/mockito_mocker.py index de0dbdb5c..184db5d01 100644 --- a/src/uipath/_cli/_evals/mocks/mockito_mocker.py +++ b/src/uipath/_cli/_evals/mocks/mockito_mocker.py @@ -4,6 +4,8 @@ from typing import Any, Callable from mockito import ( # type: ignore[import-untyped] + ARGS, + KWARGS, invocation, mocking, ) @@ -72,16 +74,20 @@ def __init__(self, context: MockingContext): mock_obj = mocking.Mock(self.stub) for behavior in self.context.strategy.behaviors: - resolved_args = _resolve_value(behavior.arguments.args) - resolved_kwargs = _resolve_value(behavior.arguments.kwargs) - - args = resolved_args if resolved_args is not None else [] - kwargs = resolved_kwargs if resolved_kwargs is not None else {} - - stubbed = invocation.StubbedInvocation(mock_obj, behavior.function)( - *args, - **kwargs, - ) + # If arguments is omitted (None), match any call signature + if behavior.arguments is None: + stubbed = invocation.StubbedInvocation(mock_obj, behavior.function)( + *ARGS, **KWARGS + ) + else: + resolved_args = _resolve_value(behavior.arguments.args) + resolved_kwargs = _resolve_value(behavior.arguments.kwargs) + args = resolved_args if resolved_args is not None else [] + kwargs = resolved_kwargs if resolved_kwargs is not None else {} + stubbed = invocation.StubbedInvocation(mock_obj, behavior.function)( + *args, + **kwargs, + ) for answer in behavior.then: answer_dict = answer.model_dump() diff --git a/src/uipath/_cli/_evals/mocks/types.py b/src/uipath/_cli/_evals/mocks/types.py index a90656c63..fb2917e4b 100644 --- a/src/uipath/_cli/_evals/mocks/types.py +++ b/src/uipath/_cli/_evals/mocks/types.py @@ -67,7 +67,7 @@ class MockingAnswer(BaseModel): class MockingBehavior(BaseModel): function: str = Field(..., alias="function") - arguments: MockingArgument = Field(..., alias="arguments") + arguments: MockingArgument | None = Field(default=None, alias="arguments") then: list[MockingAnswer] = Field(..., alias="then") diff --git a/tests/cli/eval/mocks/test_mocks.py b/tests/cli/eval/mocks/test_mocks.py index 22e4453c4..a134cd8c6 100644 --- a/tests/cli/eval/mocks/test_mocks.py +++ b/tests/cli/eval/mocks/test_mocks.py @@ -351,6 +351,7 @@ def foofoo(*args, **kwargs): with pytest.raises(NotImplementedError): assert foofoo() + assert evaluation.mocking_strategy.behaviors[0].arguments is not None evaluation.mocking_strategy.behaviors[0].arguments.kwargs = {"x": 1} set_execution_context( MockingContext( @@ -378,6 +379,56 @@ def foofoo(*args, **kwargs): assert foo(x=2) == "bar1" +def test_mockito_mockable_sync_arguments_omitted(): + """Test that omitting 'arguments' entirely matches any call""" + + # Arrange + @mockable() + def bar(*args, **kwargs): + raise NotImplementedError() + + evaluation_item: dict[str, Any] = { + "id": "evaluation-id", + "name": "Mock bar", + "inputs": {}, + "evaluationCriterias": { + "ExactMatchEvaluator": None, + }, + "mockingStrategy": { + "type": "mockito", + "behaviors": [ + { + "function": "bar", + # No "arguments" field - should match any call + "then": [{"type": "return", "value": "mocked"}], + } + ], + }, + } + evaluation = EvaluationItem(**evaluation_item) + assert isinstance(evaluation.mocking_strategy, MockitoMockingStrategy) + # Verify arguments is None when omitted + assert evaluation.mocking_strategy.behaviors[0].arguments is None + + # Act & Assert + set_execution_context( + MockingContext( + strategy=evaluation.mocking_strategy, + name=evaluation.name, + inputs=evaluation.inputs, + ), + _mock_span_collector, + "test-execution-id", + ) + + # All these should work - match any call signature + assert bar() == "mocked" + assert bar(x=1) == "mocked" + assert bar("positional") == "mocked" + assert bar(a=1, b=2, c=3) == "mocked" + assert bar("pos1", "pos2", key="value") == "mocked" + + @pytest.mark.asyncio async def test_mockito_mockable_async(): # Arrange @@ -433,6 +484,7 @@ async def foofoo(*args, **kwargs): with pytest.raises(NotImplementedError): assert await foofoo() + assert evaluation.mocking_strategy.behaviors[0].arguments is not None evaluation.mocking_strategy.behaviors[0].arguments.kwargs = {"x": 1} set_execution_context( MockingContext( diff --git a/uv.lock b/uv.lock index d750c749f..137341150 100644 --- a/uv.lock +++ b/uv.lock @@ -2491,7 +2491,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.7.5" +version = "2.7.6" source = { editable = "." } dependencies = [ { name = "applicationinsights" },