Ponownie używaj kontekstu aplikacji Spring w klasach testowych junit

84

Mamy kilka przypadków testowych JUnit (testy integracyjne), które są logicznie pogrupowane w różne klasy testowe.

Jesteśmy w stanie załadować kontekst aplikacji Spring raz na klasę testową i ponownie użyć go we wszystkich przypadkach testowych w klasie testowej JUnit, jak wspomniano w http://static.springsource.org/spring/docs/current/spring-framework-reference /html/testing.html

Jednak zastanawialiśmy się tylko, czy istnieje sposób, aby załadować kontekst aplikacji Spring tylko raz dla kilku klas testowych JUnit.

FWIW, używamy Spring 3.0.5, JUnit 4.5 i używamy Mavena do budowy projektu.

Ramesh
źródło
5
Wszystkie poniższe odpowiedzi są świetne, ale nie mam pliku context.xml. Czy opisałem swoją drogę w zapomnienie? Czy można to zrobić bez pliku context.xml?
markthegrea
2
czy znalazłeś odpowiedź na swoje rozwiązanie? Mam ten sam problem i chcę to zrobić za pomocą adnotacji i Spring Boot.
AleksandarT

Odpowiedzi:

96

Tak, jest to całkowicie możliwe. Wszystko, co musisz zrobić, to użyć tego samego locationsatrybutu w swoich klasach testowych:

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring buforuje konteksty aplikacji według locationsatrybutu, więc jeśli ten sam locationspojawia się po raz drugi, Spring używa tego samego kontekstu zamiast tworzyć nowy.

Napisałem artykuł o tej funkcji: Przyspieszenie testów integracji Spring . Jest to również szczegółowo opisane w dokumentacji Springa: 9.3.2.1 Zarządzanie kontekstem i buforowanie .

Ma to interesującą konsekwencję. Ponieważ Spring nie wie, kiedy JUnit zostanie ukończony, buforuje cały kontekst na zawsze i zamyka go za pomocą haka zamykającego JVM. Takie zachowanie (zwłaszcza gdy masz wiele klas testowych z różnymi locations) może prowadzić do nadmiernego użycia pamięci, wycieków pamięci itp. Kolejna zaleta buforowania kontekstu.

Tomasz Nurkiewicz
źródło
Ach! Nie zdawałem sobie z tego sprawy. Podążamy za tym podejściem od dłuższego czasu i (błędnie) przypisałem długi czas wykonywania testu obciążeniu kontekstu sprężynowego w każdej klasie testów. Dokładnie sprawdzę teraz. Dzięki.
Ramesh,
1
Powiedziałbym raczej, że Spring nie ma żadnej wiedzy o kolejności wykonywania Twoich testów. W rezultacie nie jest w stanie stwierdzić, czy kontekst będzie później potrzebny, czy też może zostać usunięty.
philnate
1
Nie rozumiem, jak to może być prawdą. Eclipse / JUnit spędza 2 minuty na rozgrzewaniu środowiska za każdym razem, gdy wykonuję test Run As / JUnit. Nie miałoby to miejsca, gdyby cokolwiek zostało zapisane w pamięci podręcznej.
user1944491
3
Masz jakiś pomysł, czy można to w pełni zrobić za pomocą adnotacji zamiast używania XML do definicji kontekstu? Szukałem dużo o tym w dokumencie i tutaj na SO, ale nie mogłem znaleźć niczego, co prowadzi mnie do myślenia, że ​​to nie jest możliwe.
Jean-François Savard
czy to nie jest prawdą, jeśli masz inicjatory? mój jest inicjowany dla każdego testu w klasie
Kalpesh Soni
26

Aby dodać do odpowiedzi Tomasza Nurkiewicza , od wiosny 3.2.2 @ContextHierarchyadnotacja może mieć oddzielną, skojarzoną strukturę wielu kontekstów. Jest to przydatne, gdy wiele klas testowych chce współużytkować (na przykład) konfiguracje bazy danych w pamięci (źródło danych, EntityManagerFactory, menedżer tx itp.).

Na przykład:

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("FirstTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
 ...
}

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("SecondTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
 ...
}

Mając tę ​​konfigurację, kontekst używający „test-db-setup-context.xml” zostanie utworzony tylko raz, ale elementy bean wewnątrz niego mogą zostać wstrzyknięte do kontekstu pojedynczego testu jednostkowego

Więcej informacji na temat podręcznika: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management (wyszukaj „ hierarchię kontekstu ”)

gerrytan
źródło
Mam wielomodułowego mavena i staram się uniknąć konfiguracji bazy danych w module serwisowym (ponieważ jest już załadowany testami modułu dataaccess) i nie działa dla mnie!
Muhammad Hewedy
5
To zadziałało dla mnie! Dzięki. Dla jasności, bez adnotacji @ContextHierarchy, wiosna ładuje moją bazę danych dla każdego testu. Używam parametru „classes”: @ContextConfiguration (classes = {JpaConfigTest.class, ...
Brel
5
Masz jakiś pomysł, czy można to w pełni zrobić za pomocą adnotacji zamiast używania XML do definicji kontekstu? Szukałem dużo o tym w dokumencie i tutaj na SO, ale nie mogłem znaleźć niczego, co prowadzi mnie do myślenia, że ​​to nie jest możliwe.
Jean-François Savard
1
@ Jean-FrançoisSavard czy miałeś jakieś szczęście podczas wyszukiwania (przez adnotacjew zamiast XML)?
javadev
@javadev Mam nadzieję, że właśnie tego szukasz docs.spring.io/spring/docs/current/spring-framework-reference/…
Raviteja Gubba
1

Zasadniczo spring jest wystarczająco inteligentny, aby skonfigurować to za Ciebie, jeśli masz taką samą konfigurację kontekstu aplikacji w różnych klasach testowych. Na przykład, powiedzmy, że masz dwie klasy A i B w następujący sposób:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

W tym przykładzie klasa A naśladuje fasolę C, podczas gdy klasa B symuluje fasolę D.W związku z tym spring traktuje je jako dwie różne konfiguracje i w ten sposób ładuje kontekst aplikacji raz dla klasy A i raz dla klasy B.

Jeśli zamiast tego chcielibyśmy, aby wiosna współużytkowała kontekst aplikacji między tymi dwiema klasami, musiałyby wyglądać następująco:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

Jeśli połączysz swoje klasy w ten sposób, spring załadowałby kontekst aplikacji tylko raz, albo dla klasy A, albo B, w zależności od tego, która z tych dwóch klas zostanie uruchomiona jako pierwsza w zestawie testów. Można to powielić w wielu klasach testowych, jedynym kryterium jest to, że nie należy dostosowywać klas testowych w inny sposób. Wszelkie dostosowania, które powodują, że klasa testowa różni się od innych (w oczach wiosny), kończyłyby się wiosną utworzeniem innego kontekstu aplikacji.

Saurabh Gour
źródło
0

utwórz swoją klasę konfiguracji, jak poniżej

@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class )
@SpringBootTest(classes ={add your spring beans configuration classess})
@TestPropertySource(properties = {"spring.config.location=classpath:application"})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);


    //auto wire all the beans you wanted to use in your test classes
    @Autowired
    public XYZ xyz;
    @Autowired
    public ABC abc;


    }



Create your test suite like below



@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);


}

Utwórz swoje klasy testowe, jak poniżej

public class Test1 extends RunConfigration {


  @Test
    public void test1()
    {
    you can use autowired beans of RunConfigration classes here 
    }

}


public class Test2a extends RunConfigration {

     @Test
    public void test2()
    {
    you can use autowired beans of RunConfigration classes here 
    }


}
dileep gudla
źródło
0

Godnym uwagi jest to, że jeśli użyjemy @SpringBootTests, ale ponownie use @MockBean in different test classes, Spring nie ma możliwości ponownego wykorzystania kontekstu aplikacji we wszystkich testach.

Rozwiązaniem jest to move all @MockBean into an common abstract classi to rozwiązuje problem.

@SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {

   @MockBean
   private ProductService productService;

   @MockBean
   private InvoiceService invoiceService;

}

Następnie klasy testowe można zobaczyć jak poniżej

public class ProductControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchProduct_ShouldSuccess() {
   }

}

public class InvoiceControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchInvoice_ShouldSuccess() {
   }

}
Quoc Truong
źródło