INTRODUCTION
Betamix is a tool for simulating external HTTP resources, such as web services and REST APIs, in your tests. The project was inspired by the VCR library for Ruby.
You don’t need third-party downtime, network issues, or resource constraints (like Twitter API rate limiting to break your tests). Writing your own stub web server code and configuring your application to connect to a different URI during testing is time consuming and may not accurately simulate the actual service.
Betamix aims to address these issues by intercepting HTTP connections initiated by your application and replaying previously recorded responses. If it sounds unusual to you, our experts are ready to provide software testing services to come up with great solutions.
The first time you run a test tagged with @Betamix, any HTTP traffic is recorded to tape and subsequent test runs will play the recorded HTTP response from the tape without actually connecting to an external server.
Betamix works with JUnit and Spock. Although written in Groovy, Betamix can be used to test applications written in any JVM language.
Tapes are stored on disk as YAML files and can be manually created or modified, or stored in your project’s source control repository so that they can be shared with other team members and used by your CI server. Different tests may use different tapes to simulate different trigger conditions. Each feed can contain multiple request / response interactions. An example tape file can be found here.
VERSIONS
The current stable version of Betamix is 1.1.2.
The current development version of Betamix is 1.2-SNAPSHOT.
IMPLEMENTATIONS
Betamix comes in two flavors. The first is a HTTP and HTTPS intermediary that can capture traffic made in any capacity that regards Java’s http.proxyHost and http.proxyPort framework properties. The second is a straightforward covering for Apache HttpClient.
The Betamix proxy
The proxy implementation can be used with HTTP traffic initiated from java.net.URLConnection, Apache HttpClient, etc. It runs an actual HTTP(S) proxy on Jetty and overrides the JVM proxy settings so that traffic is redirected via the proxy.
The Betamix HttpClient wrapper
The HttpClient wrapper is a simpler and faster implementation than the Betamix proxy but only works with HttpClient (or things built on top of it such as the Groovy Http Builder). It is a good choice when you use HttpClient instances that are injected into your classes. In your tests you simply inject an instance of BetamixHttpClient instead.
INSTALLATION
Stable forms of Betamix are accessible from the Maven focal vault. Stable and improvement adaptations are accessible from the Sonatype OSS Maven store. To introduce with your beloved form framework see beneath.
Please note the Maven group changed between versions 1.0 and 1.1. Make sure you are specifying the group co.freeside when referencing Betamix in your build.
If you are installing a development version you will need to add the repository http://oss.sonatype.org/content/groups/public/ to your build.
- Gradle
- Grails
- Maven
Gradle
To utilize the Betamix intermediary in a task utilizing Gradle add the accompanying reliance to your build.gradle record:
testCompile ‘co.freeside:betamix-proxy:1.1.2’
Or to use the HttpClient wrapper add this:
testCompile ‘co.freeside:betamix-httpclient:1.1.2’
USAGE
To use Betamix you just need to annotate your JUnit test or Spock specifications with @Betamix(tape=”tape_name”) and include a Recorder Rule.
If you are using the Betamix proxy you need to use ProxyRecorder rule.
- JUnit
- Spock
JUnit
import co.freeside.betamix.junit.Betamix;
import co.freeside.betamix.junit.RecorderRule;
import co.freeside.betamix.Recorder;
import org.junit.*;
public class MyTest {
@Rule public RecorderRule recorder = new RecorderRule(new Recorder());
@Betamix(tape=”my tape”)
@Test
public void testMethodThatAccessesExternalWebService() {
}
}
Recording and playback
Betamix will keep in touch with the current tape when it catches any HTTP demand that doesn’t coordinate with anything currently on the tape. On the off chance that a coordinating with recorded cooperation is found, the intermediary doesn’t advance the solicitation to the objective URI, however rather returns the recently recorded reaction to the customer.
Matching requests
By default recorded interactions are matched based on the method and URI of the request. For most scenarios this is adequate. However, you can modify the matching behaviour by specifying a match argument on the @Betamix annotation. Any combination of instances of the co.freeside.betamix.MatchRule enum can be used. If multiple rules are used then only a recorded interaction that matches all of them will be played back. MatchRule options are:
method
the request method, GET, POST, etc.
uri
the full URI of the request target. This includes any query string.
body
the request body. This can be useful for testing connections to RESTful services that accept POST data.
host
the host of the target URI. For example the host of http://search.facebook.com/search.json is search.facebook.com.
path
the path of the target URI. For example the path of http://search.facebook.com/search.json is /search.json.
port
the port of the target URI.
query
the query string of the target URI.
fragment
the fragment of the target URI. i.e. anything after a #.
headers
the request headers. If this rule is used then all headers on the intercepted request must match those on the previously recorded request.
Note that request matching is not done at all when using READ_SEQUENTIAL or WRITE_SEQUENTIAL tape modes.
Tape modes
Betamix supports different read/write modes for tapes. The tape mode is set by adding a mode argument to the @Betamix annotation.
READ_WRITE
This is the default mode. On the off chance that the intermediary catches a solicitation that coordinates with a recording on the tape then the recorded reaction is played back. Otherwise the request is forwarded to the target URI and the response recorded.
READ_ONLY
The intermediary will play back reactions from tape yet if it blocks an obscure solicitation it won’t advance it to the objective URI or record anything, rather it reacts with a 403: Forbidden status code.
WRITE_ONLY
The intermediary will consistently advance the solicitation to the objective URI and record the reaction whether or not or not a coordinating with demand is as of now on the tape. Any current recorded connections will be overwritten.
READ_SEQUENTIAL
The intermediary will replay accounts from the tape in severe successive request. If the current solicitation doesn’t coordinate with the following recorded solicitation on the tape a mistake is raised. Moreover if a solicitation shows up after every one of the accounts have effectively been played back a mistake is raised. This is primarily useful for testing stateful endpoints. Note that in this mode multiple recordings that match the current request may exist on the tape.
WRITE_SEQUENTIAL
The proxy will behave as per WRITE_ONLY except that no matching on existing requests is done. All requests are recorded in sequence regardless of whether they match an existing recording or not. This mode is intended for preparing tapes for use with READ_SEQUENTIAL mode.
Ignoring certain hosts
A regular model would be in case you are utilizing the Betamix’123x’1234r6 proxy intermediary when start to finish testing a web application utilizing something like HtmlUnit – you would not need Betamix to catch associations with localhost as that would mean traffic among HtmlUnit and your application was recorded and played back!
In such a case you can simply configure the ignoreHosts property of the co.freeside.betamix.Recorder object. The property accepts a list of hostnames or IP addresses. These can include wildcards at the start or end, for example “*.mydomain.com”.
If you need to ignore connections to localhost you can simply set the ignoreLocalhost property to true.
Editing tape files
Tape files are stored as YAML so that they should be reasonably easy to edit by hand. HTTP solicitation and reaction bodies are put away as text for most normal printed MIME types. Twofold data for things like pictures is furthermore taken care of anyway isn’t conventional to change the most difficult way possible. Now and again where the text contains non-printable characters then text information will be put away as binary.
PROXY COMPATIBILITY
If you’re using the Betamix proxy there are some compatibility issues you should be aware of:
Java 6
Under Java 6 it is not possible to proxy connections to URLs whose host is localhost or 127.0.0.1. The workaround is to use the hostname or public IP address of the machine instead. This is a known issue that is fixed in Java 7.
Apache HttpClient
The Betamix intermediary can possibly capture traffic from Apache HttpClient if the customer occurrence is set up to utilize a ProxySelectorRoutePlanner. At the point when Betamix isn’t dynamic this will mean HttpClient traffic will be steered through the default intermediary arranged in Java (assuming any).
In a dependency injection context such as a Grails app you can just inject a proxy-configured HttpClient instance into your class-under-test.
The HttpClient library gives an execution considered SystemDefaultHttpClient that utilizes the JVM intermediary settings. In a perfect world you can utilize that. Also, Betamix gives a helpful HttpRoutePlanner execution that you can use to arrange examples of other HttpClient types. For instance:
DefaultHttpClient client = new DefaultHttpClient();
BetamixRoutePlanner.configure(client);
Groovy HTTPBuilder
Groovy HTTPBuilder and its RESTClient variant are wrappers around HttpClient so the same proxy configuration needs to be applied. For example:
def http = new HTTPBuilder(‘http://groovy.codehaus.org’)
BetamixRoutePlanner.configure(http.client)
HTTPBuilder also includes a HttpURLClient class which needs no special configuration as it uses a java.net.URLConnection rather than HttpClient.
Apache HttpClient 3.x
HttpClient 3.x is no longer supported but still fairly widely used. It does not take any notice of Java’s HTTP proxy settings and does not have the HttpRoutePlanner facility that HttpClient 4.x does. This means Betamix cannot work as seamlessly. You must set the host and port of the Betamix proxy on the HttpClient instance explicitly and Betamix’s ignoreHosts and ignoreLocalhost configuration properties will be completely ignored. For example:
HttpClient client = new HttpClient();
ProxyHost proxy = new ProxyHost(“localhost”, recorder.getProxyPort());
client.getHostConfiguration().setProxyHost(proxy);
WSLite
WSLite does not use the default JVM proxy settings at all. You will need to configure it to use the Betamix proxy. There is a getProxy() convenience method on Recorder that makes this very easy:
def http = new RESTClient(‘http://freeside.co/betamix’)
def response = http.get(path: ‘/’, proxy: recorder.proxy)
HTTPS
As of version 1.1 the Betamix proxy can handle HTTPS traffic as well as HTTP. Because Betamix needs to be able to read the content of the request and response it is not actually a valid secure proxy. Betamix will only work if the certificate chain is broken. To enable HTTP support, you simply need to set the sslEnabled boolean property on the Recorder instance in your test or via Betamix configuration.
Note, this is not necessary if you are using the BetamixHttpClient wrapper class instead of the proxy. HTTPS is handled no differently to HTTP in that case.
HTTPS with Apache HttpClient
Apache HttpClient needs to be configured to use Betamix’s HTTPS support:
BetamixHttpsSupport.configure(client);
CONFIGURATION
The Recorder class has some configuration properties that you can override:
tapeRoot
the base directory where tape files are stored. Defaults to src/test/resources/betamix/tapes.
defaultMode
the default TapeMode applied to an inserted tape when the mode argument is not present on the @Betamix annotation.
ignoreHosts
a list of hosts that will be ignored by the Betamix proxy. Any requests made to these hosts will proceed normally.
ignoreLocalhost
if set to true the Betamix proxy will ignore connections to local addresses. This is equivalent to setting ignoreHosts to [“localhost”, “127.0.0.1”, InetAddress.localHost.hostName, InetAddress.localHost.hostAddress].
When using the Betamix proxy these additional properties are available:
proxyPort
the port the Betamix proxy listens on. Defaults to 5555.
proxyTimeout
the number of milliseconds before the proxy will give up on a connection to the target server. A value of zero means the proxy will wait indefinitely. Defaults to 5000.
sslEnabled
if set to true the Betamix proxy will also intercept HTTPS traffic.
sslSocketFactory
the instance of org.apache.http.conn.ssl.SSLSocketFactory that the Betamix proxy will use to connect to the target when using HTTPS. By default, Betamix will use its own socket factory with a self-signed certificate but if you are connecting to a server that has more stringent security requirements or needs SSL authentication you can override this.
If you have a file called BetamixConfig.groovy or betamix.properties somewhere in your classpath it will be picked up by the Recorder class.
Example BetamixConfig.groovy script
betamix {
tapeRoot = new File(‘src/test/resources/betamix/tapes’)
useProxy = true
proxyPort = 5555
proxyTimeout = 5000
defaultMode = TapeMode.READ_WRITE
ignoreHosts = [‘localhost’, ‘127.0.0.1’]
ignoreLocalhost = false
sslEnabled = false
sslSocketFactory = new SSLSocketFactory(myTrustStore)
}
Example betamix.properties file
betamix.tapeRoot=src/test/resources/betamix/tapes
betamix.useProxy=true
betamix.proxyPort=5555
betamix.proxyTimeout=5000
betamix.defaultMode=READ_WRITE
betamix.ignoreHosts=localhost,127.0.0.1
betamix.ignoreLocalhost=false
betamix.sslEnabled=false
CAVEATS
Security
The Betamix proxy is a testing tool and not a spec-compliant HTTP proxy. It ignores any and all headers that would normally be used to prevent a proxy caching or storing HTTP traffic. You should ensure that sensitive information such as authentication credentials is removed from recorded tapes before committing them to your app’s source control repository.
EXAMPLES
Betamix’s GitHub repository includes an example Grails application.
ABOUT
Why “Betamix”?
Betamix is a JVM port of the VCR library for Ruby. It is named after Betamix, an obsolete format of Video Cassette Recorder.
License
Apache Software Licence, Version 2.0
Issues
Please raise issues on Betamix’s GitHub issue tracker. Forks and pull requests are more than welcome.
Dependencies
Betamix depends on the following libraries (you will need them available on your test classpath in order to use Betamix):
- Groovy 1.8+
- Apache HttpClient
- SnakeYAML
- JUnit 4
In addition, the Betamix proxy requires:
- Jetty 7
If your project gets dependencies from a Maven repository these dependencies will be automatically included for you.
Author
- Rob Fletcher
Contributors
- Marcin Erdmann
- Lari Hotari
- Steve Ims
- Nobuhiro Sue
Acknowledgements
Betamix is inspired by the VCR library for Ruby written by Myron Marston. Porting VCR to Groovy was suggested to me by Jim Newbery.
HTTPS proxy support was largely the work of Lari Hotari.
The documentation is built with Jekyll, Twitter Bootstrap, LESS, Modernizr, jQuery & Google Code Prettify. The fonts are Kameron, Bitter and Source Code Pro.