Servlet container je mocné API, které kdysi pomohlo popularitě Javy mezi vývojáři. Je to ale také API staré, které samo o sobě nemá podporu pro psaní jednotkových testů. Můžeme sice pouštět testy tak, že spustíme celý servlet kontainer, ale pro mnoho případů je to zbytečné. Existuje i jednodušší řešení, pojďme se na něj podívat.

Testování Springem

API servletu je definováno pomocí rozhraní, není tedy problém vytvořit “fiktivní testovací implementace” neboli mocky. Přeci jenom to trochu práce dá, je lepší vypomoci si Springem, který implementuje tato rozhraní ve třídách MockHttpServletRequest, MockHttpServletResponse, MockHttpSession a další třídy. Testovat může následující trivální kód.


public class ServletTest {
protected Servlet getServlet(){
//... viz níže
}

protected MockHttpServletResponse service(String method, String uri) throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
req.setMethod(method);
req.setRequestURI(uri);
MockHttpServletResponse resp = new MockHttpServletResponse();
getServlet().service(req, resp);
Assert.assertTrue(resp.getStatus() + ": " + resp.getErrorMessage(), resp.getStatus() == HttpServletResponse.SC_OK);
return resp;
}

@Test
public void testGet(){
service("GET", "/");
}
}

Vytvoření servletu

Výše uvedený kód je zajímavý, ale nepoužitelný – kde vzít testovaný servlet? A kde vzít servlet, který bude napojený na Spring a bude všechny požadavky směrovat na něj? Jako třídu servletu můžeme použít DispatcherServlet – právě toto je implementace servletu ve Spring Frameworku. Musíme ji ovšem vhodně podsunout aplikační kontext, ve většině ukázek podobných kódů na internetu je použitá za tím účelem dědičnost, viz níže uvedený kód.


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/tests-context.xml")
public class RestAPITest {
// jeden servlet pro všechny testy
private static DispatcherServlet dispatcherServlet;
@Autowired
protected ApplicationContext applicationContext;

protected DispatcherServlet getServlet() throws ServletException {
if (dispatcherServlet == null) {
dispatcherServlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.setParent(applicationContext);
wac.refresh();
return wac;
}
};
dispatcherServlet.init(new MockServletConfig());
}
return dispatcherServlet;
}
}

Test odpovědi

Výše uvedené principy jsou použitelné pro testování libovolné webové aplikace postavené na Spring Frameworku. V případě REST API bychom měli testovat ještě pár věcí navíc:

  • správný http kód odpovědi pro dotaz na existující dokument a pro dotaz na neexistující dokument
  • MIME typ odpovědi
  • vykonání odpovídajících operací při volání GET/POST/PUT/DELETE
  • porovnání obsahu odpovědi s očekávanou

Porovnání obsahu odpovědi s očekávanou odpovědí nemusí být jednoduché – záleží na tom, co chceme testovat a také na tom, co chceme v testu ignorovat. Testy by měly být odolné vůči změnám v aplikaci – přidání políčka do dokumentu by nemělo shodit všechny testy. Porovnání dvou souborů tedy nepřichází v úvahu. Máme i jiné možnosti, pro jednoduchost předpokládejme, že naše REST API vrací JSON nebo XML:

  • odpověď otestujeme sadou regularních výrazů
  • odpověď prověříme sadou XPath dotazů
  • odpověď necháme deserializovat do objektů Javy a porovnáme objekty
  • vytvoříme si XML/JSON dokument podobný očekávanému a pomocí streamovací knihovny (SAX pro XML a JSON.org pro JSON) porovnáme obě odpovědi

Závěr

Testovat webovou aplikaci nebo REST API bez plnohodnotného servlet containeru je nejen možné, ale i lepší řešení – testy budou startovat a poběží rychleji a spolehlivěji. Mohli bychom testovat přímo aplikační služby skryté za REST API, ale to není vhodné – na REST API může navazovat další (cizí) aplikace, a mělo by tedy být stabilní a otestované včetně takových “detailů” jako je URI služby nebo různé kombinace MIME typů.

Související odkazy