Level up your cloud testing game with the Azure SDK test proxy

Mario Guerra

Testing is an essential step in the software development process. Finding bugs through testing before releasing software to production saves time and money over the lifetime of the product. However, testing software against a live cloud service like Azure can be costly, since services must be provisioned and maintained in order to run the tests.

The Azure SDK team does a large amount of testing prior to releasing SDK libraries. Even though we’re part of the Azure business, using Azure resources still incurs significant costs we must account for. Nothing is free after all, and running our tests requires spinning up and maintaining cloud resources that incur costs to the company.

To reduce our cloud costs, the Azure SDK team created a lightweight test proxy that records app interactions with Azure and plays them back on demand, eliminating the need to access live Azure services. The test proxy uses key features from our Azure Core library to capture, record, and play back interactions between the client and Azure services.

Two significant benefits of using recordings are that you don’t need to create mock implementations for each service interaction and unit testing is greatly accelerated.

Our internal use of the test proxy has been a huge benefit to us, and we’re excited to share this tool with the broader Azure developer community.

Although the test proxy isn’t an officially supported Microsoft product, we believe it’s useful enough to be freely available as part of our open source Azure SDK Tools GitHub repository. We welcome contributions and are grateful for any support in enhancing the usefulness of the test proxy.

If you’d like a brief overview of how the test proxy works and you can spare four minutes, watch this video first:

The test proxy provides record and playback capabilities compatible with Azure SDKs for .NET, Python, Java, JavaScript, Go, and C++.

To use the test proxy, you must be able to reroute your app requests to the test proxy via modifications to the request headers.

Although this article was written using C# constructs and syntax, the same principles apply to our SDKs for Python, Java, JavaScript, Go, and C++.

To integrate the record and playback text proxy into your testing environment, it’s important to have a basic understanding of some of the key concepts regarding how Azure SDK libraries work.

Azure SDKs

Azure SDKs are a set of language-specific libraries we offer that makes it easier for developers to interact with Azure services, such as storage, compute, and databases, by providing a consistent and easy-to-use interface.

These libraries abstract away the complexity of interacting with Azure services, allowing you to focus on building your application logic.

Azure service clients

A service client refers to the component of an Azure SDK that allows your app to interact with a specific Azure service via an API.

For example, if you want to interact with Azure Table Storage in C#, you use a Table Storage client provided by the Azure.Data.Tables library. The Table Storage client allows you to perform operations like creating, reading, updating, and deleting table resources.

HTTP pipeline

The Azure SDK HTTP pipeline is the part of a service client responsible for managing communication between your app and Azure. It establishes the connection, sends and receives HTTP messages, and acts as the gateway for the Azure SDK clients in your app to interact with Azure services. Our HTTP pipeline consists of two main components: an HTTP transport and HTTP pipeline policies.

HTTP pipeline policies are a series of steps executed for each HTTP request-response roundtrip. Each policy has a specific purpose, and it may act on a request, a response, or both. Some policy examples are a retry policy, an authentication policy, and a logging policy.

To learn more about HTTP pipeline policies, this video is a great resource.

For the remainder of this article, I’m focusing on the HTTP transport component.

HTTP transport

Every Azure SDK that adheres to our current design guidelines uses the Azure.Core shared client library for handling common functionality. The HTTP Pipeline Transport is a class in the Azure Core library that is used to send HTTP requests and receive responses. HttpPipelineTransport is an abstract class, so it must be implemented either by a service client or your own custom implementation before it can be used.

When you create an Azure service client using the Azure SDKs, the Azure Core library creates a transport object for you by default in the form of an HttpClientTransport object.

It’s possible to provide your own implementation of HttpPipelineTransport, overriding the default Azure Core HTTP pipeline. The ability to override the default HTTP transport is what allows us to insert our record and playback test proxy into the mix.

You only need one custom instance of an HttpPipelineTransport, regardless of how many Azure services you’re connecting to. All the SDK service clients can utilize the same transport.

The HttpPipelineTransport class contains three abstract methods:

  • CreateRequest() – Creates a new transport-specific instance of an http request.
  • Process(HttpMessage) – Sends the request contained by HttpMessage and sets the Response property to the response received. Process() is a synchronous, blocking call.
  • ProcessAsync(HttpMessage) – Sends the request contained by HttpMessage and sets the Response property to the response received. ProcessAsync() is an asynchronous, non-blocking call.

All three methods are defined as abstract in the base HttpPipelineTransport class, which means they all need to be implemented by you in your application.

Client options

You can override the default HTTP transport of an HttpPipelineTransport class using service client options. These service client options are passed to the service client constructor when you instantiate a service client object. All SDK clients have their own client options, but all client options are derived from a common ClientOptions class defined in Azure.Core.

To find the name of the derived ClientOptions class for the services you’re using, refer to the ClientOptions documentation and expand the list of ‘Derived’ classes to search for your service.

The demo associated with this article is using the Cosmos DB API for Table Storage, so are defining a TableClientOptions class to use for overriding the HTTP transport.

Record & playback test proxy

Now that we’ve established some context, we can talk about the test proxy. To recap, Azure SDK service client traffic is redirected to an intermediary test proxy bound to a localhost port. The rerouting is done by changing the HTTP transport utilized by the service clients. The test proxy captures and saves requests and responses during the recording process, then forwards the requests between your app and Azure. Afterwards, the recorded information can be used to simulate Azure during testing by supplying your app with the appropriate request responses.

Full test proxy installation details and documentation are available in the test proxy readme, but here’s the short version:

  1. Install .NET 6.0 or later
  2. Install the test-proxy:
    dotnet tool update azure.sdk.tools.testproxy \--global \--add-source https://pkgs.dev.azure.com/azure-sdk/public/\_packaging/azure-sdk-for-net/nuget/v3/index.json \--version \"1.0.0-dev\*\"

After installing the tool, run it in a terminal or cmd window by typing the command test-proxy in the terminal prompt. You know the test proxy is running correctly if you see output like this:

Test proxy output

Test proxy demo

Going forward I’m referring to code from a corresponding demo project, which you can get here. This project is based on an existing Cosmos DB Table Storage demo, which you should refer to for instructions on setting up a table resource to use with the test proxy demo.

Although this demo was written in C#, the same principles apply to our SDKs for other languages. Demo projects are also available for Python, Java, JavaScript, and Go.

The Test.Proxy.Transport namespace defined in TestProxyTransport.cs contains necessary code to interact with the test proxy. You can add this source file directly to your own project and add the following directive to your main project source code:

Using Test Proxy Transport Namespace

The Test.Proxy.Transport namespace consists of three classes:

  • public class TestProxyTransport : HttpPipelineTransport
    • TestProxyTransport provides custom implementations of the abstract methods defined in the HttpPipelineTransport base class and described in the HTTP transport section of this article. These custom implementations allow us to intercept and reroute app traffic sent between an app and Azure to the test proxy.
  • public class TestProxyVariables
    • TestProxyVariables encapsulates variables that store values related to the test proxy, such as connection host (localhost), connection port (5001), and mode (record/playback).
  • public class TestProxyMethods
    • TestProxyMethods contains functions to start and stop a running test proxy processing traffic between your app and Azure.

The TestProxyTransport implementations of Process() and ProcessAysnc() contain custom code that stashes the original request URI as part of the message headers and redirects the request to point to the test proxy.

Rerouting http requests to the test proxy

When a request is redirected the test proxy, the test proxy performs one of two sets of actions depending on whether it’s running in record or playback mode:

  • Record mode:
    1. Parse the request headers to obtain the original request URI.
    2. Save the request in the recording file specified by the recording ID and the recording file path.
    3. Forward the original request to Azure using the URI obtained in step 1
    4. When the Azure response arrives, save the response in the recording file as a match to the original request and return the response to the client app.
  • Playback mode:
    1. Parse the headers to obtain the original request URI.
    2. Look up the request URI in the recording file specified by the recording ID and the recording test path.
    3. Return the corresponding response saved in the recording to the client app.

Secrets like connections strings are not saved in the recording file. Secrets are sanitized before the request is saved to avoid leaking them.

You can see the sanitization for yourself by looking at the demo project recording file named TestProxyExample.json. The connection string that was used to access the Cosmos DB Table service has been sanitized from the recording:

Sanitized secrets

Finally, I’ll cover how to actually integrate the test proxy with your existing project.

It’s easy to do, requiring a minimal amount of prologue and epilogue code.

Here’s the code you need to add to your project as a prologue:

Test proxy prologue code

You can see this example prologue (and the epilogue) in the demo project file named CosmosDBTablesExample.cs.

Notice that the first thing the prologue does is load environment variables related to the test proxy from a local .env file. These variables are then used to construct the custom TestProxyTransport object.

I’ve included a simple file parser class named ParseDotEnvFile in the demo project file named ParseDotEnvFile.cs. You could use a third-party library for this same purpose if you prefer.

You don’t have to use a .env file for your environment variables, you can set them however you prefer for your environment. You also don’t need to use environment variables at all. The test proxy parameters could be hard-coded in your test case if you choose.

However, it’s possible to change the default port the test proxy binds to, so I recommend encoding these parameters as variables that can be easily modified. For more information, see the test proxy documentation

You also need to add a small epilogue to your code:

Test proxy epilogue code

The only function of the epilogue is to stop the test proxy from recording or playback.

Once stopped, the proxy is still active and running but will no longer service requests. The proxy also saves recordings in JSON format once the StopTestProxy() method is called.

Your recording WILL NOT be saved if you don’t stop the test proxy at the end of your test.

The recording file included in the demo project is provided for illustration purposes only. It can’t be used to run the demo in playback mode because the Cosmos DB Table resource it refers to no longer exists.

When you first run the demo, you need to set PROXY_MODE to record and provide a valid Cosmos DB Table Storage connection string in the .env file to generate a new recording.

Refer to this Cosmos DB Table Storage demo for instructions on setting up a table resource to use with the test proxy demo.

Once you have a valid recording, set PROXY_MODE to playback and rerun the demo.

When you’re done with the demo, stop the test proxy by pressing ‘Ctrl-C` in the terminal window where it’s running.

Conclusion

Testing is a crucial step in the software development process, including software deployed to the cloud. Testing software against a live cloud service like Azure can be costly, since services must be provisioned and maintained in order to run the tests.

The Azure SDK team has developed a lightweight test proxy that allows us to record app interactions with Azure and play them back on demand, significantly reducing our testing costs. We’re now excited to share this tool with the broader Azure development community and invite you to try it out for yourself.

The test proxy is available for use as-is as part of our open source Azure SDK Tools project and we welcome your contributions.

The test proxy provides record/playback capabilities compatible with Azure SDKs for .NET, Python, Java, JavaScript, Go, and C++. To use it in your testing, you need to be able to reroute your app requests to the test proxy via modifications to the request headers.

If you have questions about the test proxy, leave them in a comment or create an issue in the Azure SDK Tools GitHub repository with the ‘Test-Proxy’ label.

Happy coding!

0 comments

Discussion is closed.

Feedback usabilla icon