📜 ⬆️ ⬇️

Integration tests with Maven, JUnit and Spring

Unit tests are a good thing, but, in my opinion, you cannot rely only on them. Sometimes it's important to check how several classes work, and sometimes application layers together.

Our application actively uses Spring, and therefore such integration tests should work with the context of the spring.

Often, such tests can take a lot of time, even if you use an in-memory database (by the way, I checked why - we have many JPA entities with many properties in each and Hibernate initialization, which takes tens of seconds!)
')
So we need to make sure that by default these tests, unlike the usual unit tests, are not executed during the build.

So the plan is:
1. We write services, divided into two layers of the application
2. Connect them with Spring
3. Let's write a test using JUnit for the interaction of these services (real, without any mock)
4. Make sure that this test does not run by default during the build.
5. Profit!


1. We write services, divided into two layers of the application


Why two layers? I prefer to break business logic into layers (for example, persistency layer) so that the code does not turn into spaghetti. Layers have a hierarchy - who can call whom and when separating layers into different maven projects, I check that there are no forbidden dependencies.

So, we create pom.xml of the entire application with dependencies that are needed by everyone:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>myapp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <name>myapp</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-version>3.1.0.RELEASE</spring-version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> </dependencies> <modules> <module>backend</module> <module>frontend</module> <module>gui</module> </modules> </project> 


There are three modules - gui, frontend and backend (two layers of business logic).

Here is the pom.xml for the frontend:
 <?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.mycompany</groupId> <artifactId>myapp</artifactId> <version>1.0-SNAPSHOT</version> </parent> <groupId>com.mycompany</groupId> <artifactId>frontend</artifactId> <version>1.0-SNAPSHOT</version> <name>frontend</name> <dependencies> <dependency> <groupId>com.mycompany</groupId> <artifactId>backend</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project> 


As you can see, it depends on the backend, and everything else gets from the father's pom.

Here is the pom.xml for the backend:

 <?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.mycompany</groupId> <artifactId>myapp</artifactId> <version>1.0-SNAPSHOT</version> </parent> <groupId>com.mycompany</groupId> <artifactId>backend</artifactId> <version>1.0-SNAPSHOT</version> <name>backend</name> </project> 


In the backend / src / main / java / com / mycompany / service / backend directory create the following:

IBackendService1.java:
 package com.mycompany.service.backend; public interface IBackendService1 { String computeSecretString(); } 


IBackendService2.java:

 package com.mycompany.service.backend; public interface IBackendService2 { int computeSecretNumber(); } 


BackendService1.java:

 package com.mycompany.service.backend; public class BackendService1 implements IBackendService1 { @Override public String computeSecretString() { return "James Bond"; } } 


BackendService2.java:

 package com.mycompany.service.backend; public class BackendService2 implements IBackendService2 { @Override public int computeSecretNumber() { return 7; } } 


In the frontend / src / main / java / com / mycompany / service / frontend directory we create:

IFrontendService.java:

 package com.mycompany.service.frontend; public interface IFrontendService { String getAgent(); } 


FrontendService.java:

 package com.mycompany.service.frontend; import com.mycompany.service.backend.*; public class FrontendService implements IFrontendService { private IBackendService1 backendService1; private IBackendService2 backendService2; public FrontendService(IBackendService1 backendService1, IBackendService2 backendService2) { this.backendService1 = backendService1; this.backendService2 = backendService2; } @Override public String getAgent() { return backendService1.computeSecretString()+backendService2.computeSecretNumber(); } } 


2. Connect them with Spring

backend / src / main / resources / backend-beans.xml:

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> <bean id="names_provider" class="com.mycompany.service.backend.BackendService1"/> <bean id="secret_service" class="com.mycompany.service.backend.BackendService2"/> </beans> 


frontend / src / main / resources / frontend-beans.xml:

 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd "> <import resource="classpath:backend-beans.xml"/> <bean id="agent_service" class="com.mycompany.service.frontend.FrontendService"> <constructor-arg index="0" ref="names_provider"/> <constructor-arg index="1" ref="secret_service"/> </bean> </beans> 


3. Let's write a test using JUnit for the interaction of these services (real, without any mock)


In the frontend / src / test / java / com / mycompany / integration directory
FrontendServiceTest.java:

 package com.mycompany.integration; import com.mycompany.service.frontend.IFrontendService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertTrue; @RunWith(SpringJUnit4ClassRunner.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @ContextConfiguration(locations = {"classpath:frontend-beans.xml"}) public class FrontendServiceTest { @Autowired IFrontendService frontendService; @Test public void testBond() { String agent = frontendService.getAgent(); assertTrue("It should be Bond", agent.contains("Bond")); } } 


Note the annotations:
@RunWith(SpringJUnit4ClassRunner.class) - runs tests with the Spring "launcher"
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) - will create a new context for each test method
@ContextConfiguration(locations = {"classpath:frontend-beans.xml"}) - where to get the beans

there is also @ActiveProfiles(profiles = "local") - if you are using profiles.

@Autowired inject your beans into the test class. If you have more than one bean that implements a specific interface, you can replace it with @Resource(name = "session-operations")

4. Make sure that this test does not run by default during the build.

Add to pom.xml of the entire application:

  <properties> ... <systest.package>**/com/mycompany/integration/**</systest.package> </properties> <build> .... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <excludes> <exclude>${systest.package}</exclude> <exclude>**/*$*</exclude> </excludes> </configuration> </plugin> </plugins> </build> 


Now the test during the build will not run. (We also said that inner classes are not tests)

And in order to be able to run it not only by name, add a profile in which this exclude not:

  <profiles> <profile> <id>systest</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <excludes> <exclude>**/*$*</exclude> </excludes> </configuration> </plugin> </plugins> </build> </profile> </profiles> 


Now, all the tests that are in com.mycompany.integration will not run during the build, and in order to run them you need to install the maven profile “systest”, for example:

mvn -P=systest test

5. Profit!

Source: https://habr.com/ru/post/146984/


All Articles