-
Notifications
You must be signed in to change notification settings - Fork 2k
Test Proxy Migration
It is common practice in many of our SDKs to test service client code by recording HTTP requests and responses during a test run against a live endpoint and then playing back the matching responses to requests in subsequent runs.
The basic idea is to have a test server that sits between the client being tested and the live endpoint. Instead of mocking out the communication with the server, the communication can be redirected to a test server.
- Repo size - Our repos are getting big and the biggest contributor to this issue are recordings. Consolidating on a single solution and recording format makes it easier to have the storage for recordings moved outside of the main repo.
- Share Code - reusing/centralizing the general code for recording and playback. eg: test server is hard-wired for sanitization/redirection defaults
- Performance testing - eliminate server-side bottlenecks from a benchmark so instead of contacting the live service, a test service can respond with recorded/cached responses.
The roll-out strategy can be split into below phases:
- Record test recordings with the test-proxy integration
- Migrate updated recordings to assets repo
- Using test proxy going forward
Each SDK needs to re-record its test recordings using the test-proxy integration to ensure a consolidated recording format with serialized/sanitized requests and their matching responses.
-
Delete old recordings from under
src/test/resources/session-records
. (In theory it will overwrite them but it's a good idea to just be explicit.) -
To use the proxy, test classes should extend from
TestProxyTestBase
instead ofTestBase
public abstract class DocumentAnalysisClientTestBase extends TestProxyTestBase {}
-
Run tests in
Record
mode using the test-proxy integration. -
Sanitize secrets:
Default sanitizers
, similar to the use of theRecordingRedactor
is registered in theTestProxyUtils
.Custom sanitizers
can be registered usingTestProxySanitizer
for addressing specific service needs.For example, registering a custom sanitizer for redacting the value of json key
modelId
from the response body looks like the following:@Override protected void beforeTest() { List<TestProxySanitizer> customSanitizer = new ArrayList<>(); // sanitize value for key: "modelId" in response json body customSanitizer.add(new TestProxySanitizer("$..modelId", REPLACEMENT_TEXT, TestProxySanitizerType.BODY_KEY)); // add sanitizer to Test Proxy Policy interceptorManager.addRecordSanitizers(customSanitizer); }
The test proxy is much more exact about matching recordings than the old recorder. You may get errors or strange behavior when you try to playback recording, such as output like this:
Header differences:
<Ocp-Apim-Subscription-Key> is absent in record, value <REDACTED>
<Authorization> is absent in request, value <Sanitized>
This indicates that there is something different about your test in playback and record modes. Look for places where you're doing something conditional and ensure it is doing the same thing in both modes. If you think that your test is doing the same thing and you're still hitting problems, please reach out.
Previously, in lots of tests you might have seen something like this:
SomeClient builder = new SomeClientBuilder()
// httpClient being an injected parameter, etc.
.httpClient(httpClient == null ? interceptorManager.getPlaybackClient() : httpClient)
.build();
This worked because in PLAYBACK
mode, TestBase.getHttpClients()
would return null
. In the new path we want to use the same HttpClient
for record and playback. This means getHttpClients()
now always returns a client. Thus, this code should change to look like this:
SomeClient builder = new SomeClientBuilder()
// httpClient being an injected parameter, etc.
.httpClient(interceptorManager.isPlaybackMode() ? interceptorManager.getPlaybackClient() : httpClient)
.build();
Consider:
SomeClientBuilder builder = new SomeClientBuilder()
.httpClient(interceptorManager.isPlaybackMode() ? interceptorManager.getPlaybackClient() : httpClient);
if (getTestMode() != TestMode.PLAYBACK) {
builder.addPolicy(interceptorManager.getRecordPolicy());
}
return builder.buildClient();
Previously, because RecordNetworkCallPolicy
had a check for RECORD
mode this turned out to be a no-op in LIVE
mode. Arguably this is a bug, and is prohibited in the test proxy path. Simply change this to only happen in RECORD
:
SomeClientBuilder builder = new SomeClientBuilder()
.httpClient(interceptorManager.isPlaybackMode() ? interceptorManager.getPlaybackClient() : httpClient);
if (getTestMode() == TestMode.RECORD) {
builder.addPolicy(interceptorManager.getRecordPolicy());
}
return builder.buildClient();
Override beforeTest()
instead of overriding BeforeEach
to perform any set-up before each test case. Any initialization that occurs in TestBase occurs first before this.
Overriding the BeforeEach in test classes with Junit5 will result in it not being inherited.
2) Migrate updated recordings to assets repo
Migrating the test recordings to the asset repo
will enable the test proxy to work against repositories and will not require them to emplace their test recordings directly alongside their test implementations.
- Create an
asset.json
file for each SDK i.esdk/tables/assets.json
. The asset.json file basically will allow the test-proxy to restore a set of recordings to a path, then load the recording from that newly gathered data. - Moving files from the main repo to the asset repo.
This step will involve using the test proxy for actually pushing the recording assets over to the asset repo.
Once the recordings are pushed the respective SDK
asset.json
in the main repo also needs to be replaced with theupdated asset.json
holding the newTag
information.
After moving recordings to the asset repo, live and playback testing will be the same as it was in the past.
-
When running tests in Playback mode, the
test-proxy
automatically checks out the appropriate tag in each local assets repo and performs testing.
Here is the result of running tests inplayback-mode
for a couple of packages within the Java repo: -
Take a look at
.breadcrumb
file to find which folder contains the recording files for your particular SDK.
- After running tests in record mode, the newly updated recordings no longer be in the azure-sdk-for-java repo. These updates will be reflected in a git-excluded
.assets folder
at the root of the repo. - You can
cd
into the folder containing your package's recordings and usegit status
to view the recording updates. Verify the updates, and use the following command to push these recordings to theazure-sdk-assets
repo:
C:/repo/sdk-for-java/>test-proxy push -a sdk/tables/assets.json
or running the command providing the context directory
test-proxy push -a C:/repo/sdk-for-python/sdk/tables/assets.json
How to set up and use the proxy can be found here.
- After pushing your recordings, the
assets.json
file for your package will be updated to point to a new Tag that contains the updates. Include thisassets.json
update in any pull request to update the recordings pointer in the upstream repo.
More details on the asset-sync feature
The test proxy enables CLI interactions with the external assets repository using the sdk/<sdk-name>/asset.json
file.
To locally clone
or pull
assets/test-recording associated with a particular asset.json
file.
test-proxy restore --assets-json-path <assetsJsonPath>
To reset
the local copy of the files back to the version targeted in the given assets.json file.
test-proxy reset --assets-json-path <assetsJsonPath>
To push
the updated/local assets to the assets repo.
test-proxy push --assets-json-path <assetsJsonPath>
Test-Proxy maintains a separate clone for each assets.json. The recording files will be located under your repo root under the .assets
folder.
+-------------------------------+
| azure-sdk-for-java/ |
| sdk/ |
| storage/ |
| +------assets.json |
| | appconfiguration/ |
| | +----assets.json |
| | | keyvault/ |
| | | azure-keyvault-secrets |
| | | assets.json-------+ |
| | | azure-keyvault-keys | |
| | | assets.json---+ | |
| | | | | |
| | |.assets/ | | |
| | +--AuN9me8zrT/ | | |
| | <sparse clone> | | |
| +----5hgHKwvMaN/ | | |
| <sparse clone> | | |
| AuN9me8zrT--------+ | |
| <sparse clone> | |
| BSdGcyN2XL------------+ |
| <sparse clone> |
+-------------------------------+
For more details/doubts on test-proxy workings, follow Teams channel
- Frequently Asked Questions
- Azure Identity Examples
- Configuration
- Performance Tuning
- Android Support
- Unit Testing
- Test Proxy Migration
- Azure Json Migration
- New Checkstyle and Spotbugs pattern migration
- Protocol Methods
- TypeSpec-Java Quickstart
- Getting Started Guidance
- Adding a Module
- Building
- Writing Performance Tests
- Working with AutoRest
- Deprecation
- BOM guidelines
- Release process
- Access helpers