Uh oh!
There was an error while loading. Please reload this page.
New testing utilities package with mock implementations for integration testing - #503
New testing utilities package with mock implementations for integration testing#503Rodrigo Brandão (rodrigobr-msft) wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new microsoft_agents.testing package to enable in-memory/integration-style testing of agent turns (including mocked OAuth/token flows), and updates a few type annotations in existing core/protocol libraries to improve typing ergonomics.
Changes:
- Added a new
microsoft-agents-testinglibrary withTestAdapter,TestFlow, andMockUserTokenClient. - Added tests for the new
TestFlow/ adapter reply-queue behavior. - Updated adapter/protocol typing (
use()returnsSelf) and simplifiedTokenStatustype annotations.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/testing_package/test_test_flow.py | Adds async pytest coverage for TestFlow fluent chaining and adapter reply queuing. |
| tests/testing_package/init.py | Adds test package marker for the new testing-package test suite. |
| tests/_common/testing_objects/mocks/mock_user_token_client.py | Minor formatting cleanup for an existing test mock. |
| libraries/microsoft-agents-testing/setup.py | Adds packaging entrypoint and install requirements for the new testing package. |
| libraries/microsoft-agents-testing/readme.md | Adds package README for distribution/documentation. |
| libraries/microsoft-agents-testing/pyproject.toml | Adds PEP 621 project metadata for the new testing package. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/type_def.py | Introduces shared typing aliases for the testing helpers. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/test_flow.py | Implements fluent TestFlow send/assert helpers for scripted agent testing. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py | Implements the in-memory adapter that runs the normal pipeline and captures outgoing activities. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/auth/mock_user_token_client.py | Adds an in-memory UserTokenClientBase implementation for OAuth/token-flow testing. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/auth/_types.py | Adds dataclasses used as keys/records for the token mock’s internal stores. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/auth/init.py | Exports the auth testing utilities. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/_defaults.py | Adds default conversation/user/bot constants for deterministic tests. |
| libraries/microsoft-agents-testing/microsoft_agents/testing/init.py | Exports the primary testing surface area (MockUserTokenClient, TestAdapter, TestFlow). |
| libraries/microsoft-agents-testing/MANIFEST.in | Adds packaging include rules (VERSION.txt). |
| libraries/microsoft-agents-testing/LICENSE | Adds license file for distribution compliance. |
| libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py | Updates use() to return Self for better chaining/subclass typing. |
| libraries/microsoft-agents-activity/microsoft_agents/activity/token_status.py | Simplifies TokenStatus type annotations (relies on camel-case alias generator). |
| libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py | Updates protocol use() return type to Self for accurate fluent typing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 28 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
libraries/microsoft-agents-testing/setup.py:10
- When VERSION.txt is missing and PackageVersion isn't set, package_version falls back to "0.0.0", which makes install_requires pin
microsoft-agents-hosting-core==0.0.0and will typically fail dependency resolution for local/source installs. Also,aiohttpis declared as a hard dependency but this package doesn't import it anywhere (only setup.py references it), which unnecessarily expands the install surface.
package_version = environ.get("PackageVersion", "0.0.0")
libraries/microsoft-agents-testing/microsoft_agents/testing/auth/mock_user_token_client.py:356
exchange_token()usesisto compare the_RAISE_EXCEPTIONsentinel. Since this is a string value stored in a dict, identity comparison is not guaranteed; use equality so the sentinel check is reliable.
if key in self._exchangable_tokens:
token = self._exchangable_tokens[key]
if token is _RAISE_EXCEPTION:
raise Exception("Simulated exception during token exchange.")
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:232
process_activity()overwritesactivity.idunconditionally. This breaks scenarios where tests intentionally supply an ID (and also causes IDs to be generated twice forsend_text_to_bot(), sincecreate_activity()already assigns an ID). Only assign an ID when the inbound activity doesn't already have one.
activity.recipient = self._conversation.agent
activity.conversation = self._conversation.conversation
activity.service_url = self._conversation.service_url
activity.id = self._gen_id()
tests/hosting_msteams/helpers.py:41
return [ResourceResponse()] * len(activities)repeats the sameResourceResponseinstance for every activity. If any caller mutates a response (or inspects IDs), this can produce incorrect behavior. Return a distinctResourceResponseper activity instead.
async def send_activities(self, context, activities):
self.sent_activities.extend(activities)
return [ResourceResponse()] * len(activities)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 30 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
libraries/microsoft-agents-testing/microsoft_agents/testing/auth/mock_user_token_client.py:356
- In MockUserTokenClient.exchange_token(), the sentinel check uses identity comparison (
is) against a string constant. String interning is not guaranteed, soraise_on_exchange_request()may not reliably trigger the simulated exception.
if key in self._exchangable_tokens:
token = self._exchangable_tokens[key]
if token is _RAISE_EXCEPTION:
raise Exception("Simulated exception during token exchange.")
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:232
- TestAdapter.process_activity() always overwrites
activity.idwith a new generated ID. This discards IDs already set by callers (including activities created by create_activity()) and also advances the adapter's deterministic counter unnecessarily.
activity.recipient = self._conversation.agent
activity.conversation = self._conversation.conversation
activity.service_url = self._conversation.service_url
activity.id = self._gen_id()
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 34 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
libraries/microsoft-agents-testing/microsoft_agents/testing/auth/mock_user_token_client.py:356
exchange_tokencheckstoken is _RAISE_EXCEPTION, but_RAISE_EXCEPTIONis a string sentinel. Identity comparison on strings is not reliable and can fail even when the values are equal, so the mock may silently return a token instead of raising the simulated exception. Use==for the sentinel comparison (or switch to a unique object sentinel).
if key in self._exchangable_tokens:
token = self._exchangable_tokens[key]
if token is _RAISE_EXCEPTION:
raise Exception("Simulated exception during token exchange.")
libraries/microsoft-agents-testing/setup.py:18
aiohttpis declared as an install requirement formicrosoft-agents-testing, but there are noaiohttpimports/usages in this package (only referenced here in setup.py). This adds an unnecessary runtime dependency footprint for test utilities; consider removing it unless there's a concrete API that requires it.
setup(
version=package_version,
install_requires=[
f"microsoft-agents-hosting-core=={package_version}",
"aiohttp>=3.11.11",
],
)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
libraries/microsoft-agents-testing/setup.py:17
aiohttpis listed as an install requirement formicrosoft-agents-testing, but nothing in this package imports/uses it. Keeping unused runtime dependencies increases install size and can introduce avoidable dependency conflicts.
install_requires=[
f"microsoft-agents-hosting-core=={package_version}",
],
)
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:384
get_next_reply_async()appends a Future to_queued_requestsbut never removes it if the caller times out/cancels (e.g., viaasyncio.wait_for). This can leak cancelled futures and can also block immediate dequeuing because_queued_requestsstays non-empty even when replies are queued.
loop = asyncio.get_running_loop()
future: asyncio.Future[Activity] = loop.create_future()
self._queued_requests.append(future)
return await future
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:405
create_activity()builds an Activity shaped like the current conversation, but it does not setchannel_id. Setting it makes the returned Activity self-contained for callers that usecreate_turn_context(create_activity(...))without going throughprocess_activity().
return Activity(
type=ActivityTypes.message,
text=text,
locale=self.locale or _DEFAULTS._LOCALE,
recipient=self._conversation.agent,
tests/hosting_msteams/helpers.py:43
[ResourceResponse()] * len(activities)returns the sameResourceResponseinstance repeated, so mutating one entry would mutate all of them. Returning distinct instances avoids surprising shared-state in tests.
async def send_activities(self, context, activities):
self.sent_activities.extend(activities)
return [ResourceResponse()] * len(activities)
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 34 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (7)
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:232
- process_activity unconditionally overwrites activity.id, but create_activity() already assigns an ID. This double-increments the adapter's ID counter and discards any caller-supplied inbound activity ID, which can make activity ID assertions flaky. Only generate an ID when one isn't already present.
activity.recipient = self._conversation.agent
activity.conversation = self._conversation.conversation
activity.service_url = self._conversation.service_url
activity.id = self._gen_id()
libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py:8
- Importing Self directly from typing_extensions makes runtime availability depend on typing_extensions being installed even on Python versions (3.11+) where typing.Self exists. Prefer importing Self from typing with a fallback to typing_extensions for Python 3.10, to avoid an unnecessary hard dependency on typing_extensions for newer runtimes.
from typing import Protocol, Callable, Awaitable, Optional
from typing_extensions import Self
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py:8
- Importing Self directly from typing_extensions makes runtime availability depend on typing_extensions being installed even on Python versions (3.11+) where typing.Self exists. Prefer importing Self from typing with a fallback to typing_extensions for Python 3.10, to avoid an unnecessary hard dependency on typing_extensions for newer runtimes.
from __future__ import annotations
from typing_extensions import Self
from abc import ABC, abstractmethod
libraries/microsoft-agents-testing/microsoft_agents/testing/test_flow.py:228
- assert_reply_contains accepts a description override but currently drops it when delegating to assert_reply, so failures won't show the caller-provided message. Pass description through so the error text is consistent with other assertions.
async def validate(reply: Activity) -> None:
if expected not in (reply.text or ""):
raise AssertionError(
description
or f"Expected reply text to contain '{expected}', received '{getattr(reply, 'text', None)}'."
)
return self.assert_reply(validate, timeout=timeout)
libraries/microsoft-agents-testing/microsoft_agents/testing/test_adapter.py:282
- TurnContext.send_activities([]) is allowed (it can legitimately result in an empty output list), but TestAdapter.send_activities currently raises for an empty activities list. This makes tests fail if code calls context.send_activities([]) or similar no-op sends. Return an empty response list instead of raising.
if not activities:
raise ValueError("Activities list cannot be empty.")
tests/hosting_msteams/helpers.py:43
- Using list multiplication here returns the same ResourceResponse instance repeated N times. If any code later mutates a response object, all entries will appear to change together. Prefer a list comprehension to create distinct instances.
async def send_activities(self, context, activities):
self.sent_activities.extend(activities)
return [ResourceResponse()] * len(activities)
libraries/microsoft-agents-testing/microsoft_agents/testing/auth/mock_user_token_client.py:46
- The attribute name
_exchangable_tokensis misspelled (should be_exchangeable_tokens). This is internal but it makes the code harder to read/search and increases the chance of future typos. Consider renaming it (and updating all references in this module).
_user_tokens: dict[UserTokenKey, str]
_exchangable_tokens: dict[ExchangeableTokenKey, str]
_magic_codes: list[TokenMagicCode]
def __init__(self):
"""Create an empty in-memory token store."""
self._user_tokens = {}
self._exchangable_tokens = {}
self._magic_codes = []
This pull request introduces the new
microsoft_agents.testingpackage, providing a comprehensive test utility suite for Microsoft Agents, and makes improvements to typing and protocol definitions in related libraries. The key changes include the addition of a mock user token client for testing OAuth/token flows, improvements to type annotations for better clarity and type safety, and the inclusion of licensing and packaging files for the new testing package.Testing utilities (new package):
microsoft_agents.testingpackage, includingMockUserTokenClient,TestAdapter, andTestFlowto facilitate in-memory testing of user tokens and OAuth flows without external dependencies. This includes a full-featured mock forUserTokenClientBaseand supporting data structures. [1][2][3][4][5]LICENSE) and packaging (MANIFEST.in) files, along with copyright headers, to ensure compliance and proper distribution of the new testing utilities. [1][2]Type annotations and protocol improvements:
usemethod in bothChannelAdapterProtocolandChannelAdapterto useSelffromtyping_extensionsfor more accurate type annotations, improving subclassing and chaining support. [1][2][3][4]TokenStatus, removing unnecessary imports and updating type hints to use standard Python union syntax (e.g.,str | None). [1][2]