czwartek, 4 czerwca 2009

Springframework @ContextConfiguration - podpinamy kontekst do testów

Jak ważne jest testowanie wie każdy programista, testy jednostkowe można pisać po stworzeniu aplikacji, lub co zalecane w trakcie jej tworzenia. Samo pisanie testów nie jest męczącym zajęciem o ile tworzymy niewielki projekt z odseparowanymi od siebie klasami. Problem zaczyna wzrastać do rangi misji niemożliwej gdy jedna klasa połączona jest z innymi klasami nie zawsze jawnie zadeklarowanymi jak to ma miejsce w aplikacjach opartych o Springframework.

W takim momencie z pomocą przychodzi nam adnotacja @ContextConfiguration zawarta w pakiecie org.springframework.test. Dzięki niej możemy bezpośrednio do naszej klasy testu jednostkowego podpiąć skonfigurowany wcześniej kontekst aplikacji zawierający zadeklarowany beany.



Aby zadeklarować załadowanie kontekstu wystarczy dodać adnotację i wskazać plik z kontekstem, prosta klasa która ma taką konfigurację wygląda tak:
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/WEB-INF/applicationContext.xml")
public class CategoryControllerTest extends AbstractJUnit4SpringContextTests{

}

Super, ale jak to działa? Okazuje się, że jak zwykle nie wszystko działa tak jak napisano. Gdy ja próbowałem podłączyć taki kontekst cały czas jvm wyrzucał mi błąd:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:255)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:267)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:155)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:263)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:265)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:210)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at com.intellij.rt.junit4.Junit4TestMethodAdapter.run(Junit4TestMethodAdapter.java:62)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [web-servlet.xml]; nested exception is java.io.FileNotFoundException: class path resource [web-servlet.xml] cannot be opened because it does not exist
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:349)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:81)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:226)
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:251)
... 23 more
Caused by: java.io.FileNotFoundException: class path resource [web-servlet.xml] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:142)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
... 32 more

Nie trzeba Sherlocka aby od razu dojść do wniosku, że aplikacja nie znajduje naszego pliku kontekstu. Przeszukanie google dało kilka rozwiązań ale okazały się błędne, na szczęście rozwiązanie jest proste, zamiast podawać ścieżkę do pliku, wystarczy wskazać aby kontekst został załadowany z konkretnego pliku w dowolnej ścieżce.
Wygląda to następująco:
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:**/applicationContext.xml"})
public class CategoryControllerTest extends AbstractJUnit4SpringContextTests{

}

Po takiej definicji nasz kontekst będzie ładowany poprawnie.

Update:


Jeśli jednak i to nie zadziała, możecie skopiować odpowiednie pliki kontekstów do folderu z testami (lub folder nad) i zmienić adnotację na:
@ContextConfiguration(locations = {"../test-context.xml"})


Update 2:


Localizację plików kontekstowych można podać również w formie:


@ContextConfiguration({"classpath:**/war/WEB-INF/web.xml"})

Czyli wraz z folderem głównym w którym znajduje się aplikacja, często pomaga jak to miało miejsce w moim najnowszym projekcie.

4 komentarze:

  1. Did not work for me.

    OdpowiedzUsuń
  2. Z tego co pamiętam to Spring TestContext Framework domyślnie szuka contextu na classpath.
    Można to oczywiście przeciazyc.

    Dodatkowo warto pamiętac, żew wersji 2.5.X context ktory jest tworzony nie nadaje sie do testowania aplikacji webowych (tworzony jest bodaj GenericApplicationContext a nie WebApplicationContext)

    OdpowiedzUsuń
  3. miluch - zauważyłem to, mi pomogło skopiowanie plików konfiguracyjnych do folderu z testami, choć jest to rozwiązanie raczej o kant dup*. Takie chwilowe.

    OdpowiedzUsuń
  4. Mi też niestety nie zadziałało przy spring 3.0 może jest coś innego ?

    OdpowiedzUsuń