Při psaní jednotkových testů webové aplikace je někdy potřeba spustit celý kontext servlet kontaineru v prostředí http serveru. Pojďme se podívat, jak to.
Proč Jetty?
Pro testování jsme na našem projektu použili javovový webový server Jetty. Jetty má několik vlastností, které se hodí pro použití v produkčním prostředí, tyto vlastnosti o to více vyniknou při psaní a spouštění jednotkových testů:
- snadno použitelné API
- dostupný přes centrální maven repository
- je open source a zdarma
- nenáročný na prostředky (pamět, CPU…)
Jak na to
Nejprve musíme přidat do projektu JAR soubory webového serveru, například pomocí Mavenu rozšířením pom.xml
<dependency> <groupid>org.mortbay.jetty</groupid> <artifactid>jetty</artifactid> <version>6.1.26</version> <scope>test</scope> </dependency>
Pojďme se podívat na to, jak Jetty spustit. Jde o jednoduchý kód – v tomto případě napsaný tak, že server bude očekávat požadavky na portu 8080. Je také možné server nakonfigurovat tak, že si sám zvolí náhodně port, který je volný.
public abstract class JettyBasedTest { public static final int HTTP_PORT = 8080; private Server server; @Before public void startServer() server = new Server(0); SocketConnector connector = new SocketConnector(); connector.setPort(HTTP_PORT); server.setConnectors(new Connector[] { connector }); String webAppDirectory = new File(".").getCanonicalPath() + "\\src\\main\\webapp" WebAppContext ctx = new WebAppContext(webAppDirectory, "/myapp"); server.addHandler(ctx); server.start(); } @After public void stopServer(){ server.stop(); } }
Výše uvedený kód je jen ukázkový – pochopitelně je lepší nespouštět server před každým testem a nezastavovat po každém testu, ale použít například TestSuite.
Aby byl můj příklad úplný, přidávám ještě kód jednoduchého testu. Jde o příklad testu REST API rozhraní pomocí Spring RestTemplate:
public class AnyJettyTest extends JettyBasedTest{ @Test public void testGet(){ RestTemplate client = new RestTemplate(); String retVal = client.getForObject(new URI("https://localhost://" + HTTP_PORT + "/myapp/gen"), String.class); Assert.notEmpty(retVal); } }
Spring Dependency Injection
Výše uvedenému testu něco chybí – ověření, že na serverové straně testovaný kód udělal to co měl. Jak na to? Pokud aplikace využívá Spring, můžeme nainjektovat aplikační beanu a zeptat se jí na stav. Kód je triviální – nejprve vyzvedneme kontext Springu, který je uložený v ServletContextu, a pak již snadno necháme nainjektovat beany do testu.
public class AnyJettyBasedTest extends JettyBasedTest { @Autowired UserService userService; @Before public void resolveDependencyInjection(){ // kontext servlet containeru ServletContext ctx = server.getServletContext(); // najdi kontext Springu WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(ctx); // injektuj ctx.getAutowireCapableBeanFactory().autowireBean(this); } }
Závěr
Použití plnohodnotného servlet containeru a spuštění celé aplikace není ideální řešení – z jednotkového testu se stane test integrační. Přesto takový test má smysl – například ověříme, jestli je aplikace spustitelná jako celek.
Jiný způsob použití je pro testování technnologií, které jsou špatně testovatelné a pro svůj běh vyžadují funkce, které třídy mockovaného servlet containeru neimplementují. Například Spring Security (je postaveno na filtrech servlet containeru) nebo některé způsoby použití Spring MVC (používá interní přesměrování pro přechod k view).
18.2.2011 at 08:05
Díky, takové shrnutí jsem potřeboval. Odmazávám si jednu položku z TODO.