Roman Valina
5.9.2011

Jak na mock HTTP serveru



Pro jednoho z naších zákazníků dodáváme C2DM – zasílání notifikací na mobilní telefon s operačním systémem Android. Tato služba je implementována jako HTTP request, ve kterém pošlete zprávu, registrační identifikátor instance zařízení s androidem a C2DM server se postará o doručení.

Narazil jsem ale na problém jak vytvořit unit test, který ověří správný formát odesílané notifikace. Problém je právě v tom, jak namockovat chování C2DM serveru bez toho, abych musel někde rozbíhat Jetty či nedejbože Tomcat. Testovaný kód vypadá nějak takto:

private static int sendMessage(String message, URL url) throws IOException {
  // create connection 
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  connection.setDoInput(true);
  connection.setDoOutput(true);
  connection.setRequestMethod("POST");

  // post data 
  DataOutputStream output = new DataOutputStream(connection.getOutputStream());
  output.writeBytes("message="message);
  output.flush();
  output.close();

  // establish connection 
  connection.connect(); 

  // return errocode 
  return connection.getResponseCode();
} 

 Umím vytvořit mock interfacu (viz. EasyMock , PowerMock ect.), dokonce i stub konkrétní třídy. Final třídu

URL zkrátka nepodědíte a testovanému kódu pak „nepředhodíte“. Narazil jsem na knihovnu simple, která velice jednoduše na několika řádcích na pár sekund rozběhne „webový server“. Vy pak můžete snadno nasimulovat chování protějšího serveru. Vytvoříte třídu implementující interface org.simpleframework.http.core.Container

public class MockC2dmServer implements Container {
  protected String capturedMessage;
  
  public void handle(Request request, Response response) {
    try {
      capturedMessage = request.getParameter("message");
    } catch (IOException e1) {
      capturedMessage = null;
    } 
    if(capturedMessage != null) {
      response.setCode(HttpURLConnection.HTTP_OK);
    } else {
      response.setCode(HttpURLConnection.HTTP_BADREQUEST);
    }
  }
}

 Samotné vytvoření serveru a test pak může vypadat nějak takto:

@Test public void basicSenderTest() throws Exception {
  MockC2dmServer mockServer = new MockC2dmServer();
  // binds server to socket on adress
  Connection connectionServer = new SocketConnection(mockServer);
  SocketAddress address = new InetSocketAddress("localhost", 8080));
  connectionServer.connect(address);
  // test method
  String message = "message text";
  C2dmNotificationSender.sendNotification(message, new URL("http://localhost:8080");
  Assert.assertEquals(message, mockServer.capturedMessage);
}

 Uznávám, že tento způsob testování má svá rizika, například vám nikdo nezaručí, že port 8080 bude volný, to však jde minimalizovat například metodou, která vám nějaký volný port najde, ale to už není tématem tohoto článku. Pokud někdo z vás přijde na jiné, lepší řešení rád si ho přečtu v diskuzi pod článkem.

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • Když jsem řešil stejný problém, tak jsem narazil na to, že už to někdo řešil: http://stackoverflow.com/questions/393099/mocking-http-server (ve finále jsem použil Jetty)

  • Jara H.

    Nestacilo by misto vytvareni (a pretypovani) url.openConnection() primo v sendMessage() delegovat na nejakou ConnectionFactory? V testech by se pouzila implementace ConnectionFactory, ktera vraci MockHttpURLConnection. MockHttpURLConnection by mohla byt konfigurovatelna, takze by se lepe testovaly krajni pripady. Jako neocekavany respondeCode atd. Potom by stacilo jednotkove (v izolaci) otestovat opravdovou ConnectionFactory, ze vraci spravne HttpUrlConnection.

  • Libor

    Dělám to přesně jak navrhuje Jára: [sourcecode langue="java"][/sourcecode] public class MockURL { public static void main(String[] args) { final URLConnection mockConnection = EasyMock.createNiceMock(URLConnection.class); URL stubUrl = new URL("protocol", "server", 9999, "/path", new URLStreamHandler() { @Override protected URLConnection openConnection(URL u) throws IOException { return mockConnection; } }); EasyMock.expect(mockConnection.getInputStream()).andReturn(MockURL.class.getResourceAsStream("some path")) .once(); EasyMock.replay(mockConnection); URLConnection fakeConnection = stubUrl.openConnection(); assertSame(mockConnection, fakeConnection); fakeConnection.getInputStream().read(); EasyMock.verify(mockConnection); } } [sourcecode][/sourcecode]