Published on

Betamax as presentet on GR8Conf EU 2012

  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • \n
  • As you scroll around AJAX requests are triggered to pull in TV listings\nData from REST services\nChannel list\n‘Chunks’ of programme data (4 hours x 10 channels)\nIndividual programme details\nWe have tests for…\nAll kinds of data conditions\nError handling, 404s, 503s\nEmpty responses\n
  • Want to talk about the problems Betamax addresses…\n\nIn unit tests components-under-test should be isolated\nEven end-to-end shouldn’t rely on resources outside your control\nHTTP makes tests brittle:\n3rd party outages break your tests\nResource limits break your tests\nCorporate network environment break your tests\nCoding on the go breaks your tests\nCan only test edge cases accidentally:\nwhat does your client do when… server is down?\nrate limit hit?\nit gets an invalid response?\nThis is *really* unacceptable on CI servers\n\n
  • Spin up an in-process instance of Sun webserver or jetty\n\nIt’s tedious to hand-craft responses\nTemptation is to oversimplify\n\nSmall differences may matter (headers, text encoding, content length)\nReinventing the wheel\nApplication needs configurable URLs or you have to DNS hack\n
  • May not be easy to inject mock\neasy for DI collaborators like an HttpClient instance\nnot so easy for new URL("").text\nHttpClient not the most mock-friendly API\nRemember… every time a mock returns a mock a fairy dies\n
  • Repeatable:\nreturn consistent, predictable responses not just whatever server happens to be returning right now\n\nReliable:\nnot affected by 3rd party downtime\nnot affected by network availability\n\nRealistic:\nsimulate the real response correctly\n
  • Layer on top of Ruby HTTP mocking libraries like WebMock\nUses the metaphor of a ‘cassette’ with recorded responses\n
  • Goals:\nIdeally not just Groovy - JVM\nHandle various ways of connecting:\nURL\nURLConnection\nHttpClient\nHTTPBuilder\nChallenges:\nCan’t use Groovy MOP in non-Groovy scenario\nEven in Groovy the underlying code where HTTP connection is made is often Java\nAOP might work for DI but can’t intercept new URL("").text\nCan’t even use metaClass as Groovy implementation written in Java\nThe name - Why Betamax? \n
  • Can intercept just about anything\nEasy to enable and disable programatically with system properties\nEven proxy is problematic:\nHttpClient ignores it by default\nHTTPS - it’s a man-in-the-middle attack\nProxy implemented with embedded Jetty\nTried different things but used because of lifecycle callbacks\nvert.x would be even nicer but would require Java 7\n\n\n
  • Explain what’s going on here:\nTwitterService makes REST connections to Twitter - let’s assume with URL but could be anything\n\n@Rule - JUnit\n@Betamax annotation on each test\nMultiple tests can share tapes\nTapes can hold multiple recordings\nPer-test configuration via annotation\nExplain request matching\nDefault method & uri\nDefault ideal for REST\nCan use other things; path, headers, request content, etc. (next slide)\n\n
  • Nothing different\nJUnit rules work in Spock\n
  • Default = method and uri\nIdeal for connecting to REST services\nMatch any request using any method going to the same host\nMatch requests based on POSTed data\nDifferent responses for different headers\ntest response to cache control headers\nsimulate different responses for different user-agent strings\nimprovements to be made - currently it matches all the headers\nIgnore query string\nuseful if requests add things like cache-busters or session ids (when not relevant to test)\n
  • 1 - default - can record new exchanges or play back existing ones\n2 - useful when you want to ensure you’ve captured everything - proxy returns error if recording not found\n3 - Overwrite mode - any existing recordings are overwritten\n
  • Why YAML?\nTried JSON - delimiter collision\nEditing\nBinary\n
  • Where does Betamax fit into your test architecture?\nUsing Betamax doesn’t mean you don’t need to isolate your HTTP interactions\nBuild a higher-level API around the HTTP connection code\ntest that with @Betamax\nmock it out in other code that depends on it\nDon’t just slap @Betamax on every test\nHowever, Betamax can be useful in end-to-end tests\n\n“You’re probably thinking…”\n
  • \n
  • Current status: early days but fully usable\n1.1-SNAPSHOT\n\nHTTPS support\nPrettified response bodies in tapes?\nVCR feature envy…\nCustom recording matching\nSpecific headers or body content\nGroovy expressions in tape files?\nVCR allows ERB expressions in cassette files\nSnakeyaml allows for this kind of thing\nWould have made a good hackergarten project\nAuto-expiring tapes?\nSimple HttpClient decorator as an alternative to the proxy?\nCould use MOP to intercept URL.text\nSolution for URL.openConnection?\n\n
  • Example Grails app included\n
  • Betamax

    1. 1. βetamax Rob FletcherFreeside Software / Energized Work
    2. 2. The impetus
    3. 3. HTTP connections in tests
    4. 4. Solution 1: Fake HTTP Server
    5. 5. Solution 2: Mock HTTP layer
    6. 6. Tests need to be…
    7. 7. Tests need to be… Repeatable Reliable Realistic
    8. 8. Meanwhile in Ruby…
    9. 9. Porting to JVM / Groovy
    10. 10. The Betamax Proxy
    11. 11. Using Betamax: JUnitclass TwitterServiceTest { @Rule public Recorder recorder = new Recorder() def twitterService = new TwitterService() @Betamax(tape = "twitter ok") @Test void searchesTweets() { def tweets ="#cocktailclub") assert tweets.size() == 20 assert tweets.text.every { it =~ /#cocktailclub/ } }}
    12. 12. Using Betamax: Spockclass TwitterServiceSpec extends Specification { @Rule Recorder recorder = new Recorder() def twitterService = new TwitterService() @Betamax(tape = "twitter ok") def "searches tweets"() { when: def tweets ="#cocktailclub") then: tweets.size() == 20 tweets.text.every { it =~ /#cocktailclub/ } }}
    13. 13. Matching Requests to Tapesimport static betamax.MatchRule.*@Betamax()@Betamax(match = host)@Betamax(match = [method, uri, body])@Betamax(match = [method, uri, headers])@Betamax(match = [method, host, path])
    14. 14. Tape modesimport static betamax.TapeMode.*@Betamax(mode = READ_WRITE)@Betamax(mode = READ_ONLY)@Betamax(mode = WRITE_ONLY)
    15. 15. Tape Files
    16. 16. Testing Practices
    17. 17. An example would be nice right now…
    18. 18. The future of
    19. 19.