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 byHttpMessage
and sets theResponse
property to the response received.Process()
is a synchronous, blocking call.ProcessAsync(HttpMessage)
– Sends the request contained byHttpMessage
and sets theResponse
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:
- Install .NET 6.0 or later
- 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 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:
The Test.Proxy.Transport
namespace consists of three classes:
public class TestProxyTransport : HttpPipelineTransport
TestProxyTransport
provides custom implementations of the abstract methods defined in theHttpPipelineTransport
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.
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:
- Parse the request headers to obtain the original request URI.
- Save the request in the recording file specified by the recording ID and the recording file path.
- Forward the original request to Azure using the URI obtained in step 1
- 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:
- Parse the headers to obtain the original request URI.
- Look up the request URI in the recording file specified by the recording ID and the recording test path.
- 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:
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:
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:
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