From: Phlip Date: 2005-09-23T07:16:41+09:00 Subject: Mock the Server [A repost from the TFUI mailing list] TFUI: This pattern looks obvious, so why haven't I seen more of it? To test-first the behavior of a Servlet or CGI web application, you ought to cover the interaction between your server, URLs, and your application's responses. That sounds silly. An URL is just a protocol://server/applet?parameter=value thing. These are designed to change easily; and defects will be obvious and easy to understand. You wouldn't, for example, test-first that a function takes certain simple arguments in a certain order; that's obvious. URLs are part of your usability envelop. Suppose you have a Wiki, where the URLs change on the fly and must follow special rules. Now suppose your Wiki adapts to two different kinds of servers - servlet-style and CGI-style. So these two URLs hit the same page: http://localhost:8001/SamplePage http://localhost:8001/cgi-bin/wiki.cgi?SamplePage Now suppose our Wiki can embed tags, so hitting an URI of /SamplePage#tag will jump the browser to that tag. I want the Wiki to do something with the #tag, but it can't because web browsers snarf the tag before submitting the URL. The server never sees it. So I want to upgrade the Wiki source to accept tags like this: http://localhost:8001/SamplePage!tag http://localhost:8001/cgi-bin/wiki.cgi?SamplePage!tag A page will say to jump there. Because the Wiki-side software can see the tag, it can do extra things like open test cases at that tag. This, in turn, allows you to link directly to test cases from within your wiki, like this: TestWikiFormat!internal_links. So here goes. We use Webrick, a nifty little portable web server built for extensing. To handle each HTTP GET, you provide a function called do_GET(request, response). The request object connects to the GET parameters (including the URL), and the response contains a data bucket for you to fill with an outgoing page. class MockHTTPRequest def initialize(nu_path) @path = nu_path @path_info = '' @script_name = '' @query_string = '' end attr_writer :path_info attr_reader :path, :path_info, :script_name, :query_string end I could have also used a dynamic Mock Object that mocks anything. To write that object, I just added empty members until the syntax errors went away. The Mock Object would have automagically done the same thing. Now we create a Webrick server, switched to mock mode: def servePage(pageName) logger = WEBrick::Log.new(nil, WEBrick::Log::FATAL) server = WEBrick::HTTPServer.new( :Logger=>logger, :DoNotListen => true ) server.mount '/', MiniSerlet, '' aServlet = MiniServlet.new(server,nil) request = MockHTTPRequest.new('/' + pageName) response = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP) aServer.do_GET(request, response) return response end Note the :DoNotListen symbol. That tells the system this is just a Mock Server, not a real server. It only exists so our production MiniServlet can construct with it. Webrick's server has the ability to Mock built into it. A more advanced test could abuse that server with more aggressive scenarios. This test just uses that very convenient server to avoid writing another, more complex Mock object. The servePage() fixture then creates a mock Request and a real HTTPResponse object. We pass the URI into the mock request, and call the target do_GET() method. That is the Activate step of the sequence Assemble, Activate, Assert. Here's a test case to show we hit the front page correctly: def test_miniServerLikesTheFrontPage() response = servePage('FrontPage') assert_equal 200, response.status = 200 assert_equal "text/html", response['Content-Type'] assert_match /title.FrontPage..title/, response.body end To test-first a !tag, we clone that test and change the test resource 'FrontPage' to 'FrontPage!tag'. That will force us to edit the contents of do_GET() to accept the new URL. Now we need to test CGI. Webrick can mount a CGI handler as one of its servlets. Here's the non-mock server doing it: server = WEBrick::HTTPServer.new( :ServerName => host, :Port => port, :CGIInterpreter => "C:/Ruby/bin/ruby.exe" # TODO search for the ruby binary ) server.mount '/cgi-bin', WEBrick::HTTPServlet::FileHandler, 'cgi-bin' server.mount '/', MiniServlet, '' From there, it's a short step to mount the CGI handler on the Mock Server, and then re-use those tests (as the Abstract Test Pattern) to also hit the CGI version of our new URL. This lecture has shown the principle "Just Another Object" in action. The server is just an object; it is not a special thing exempt from construction, extension, and destruction like other objects. Unless your environment couples the servlets and server to each other and everything else, so mocking them is impossible... -- Phlip http://www.greencheese.org/ZeekLand <-- NOT a blog!!!