JBT
JBT

Reputation: 8756

How to do in-container testing of a web app in Spring?

First of all, I am new to Spring.

Suppose you are developing a web app and you want to do an in-container functional testing of a http request that should get a response of string "Hello, World!". For example, let's say the web app will be deployed to Tomcat. What I want to achieve is like this: when the test starts, the app will be deployed to some kind of embedded Tomcat. Then, you can use an HTTP client (e.g., a Jersey client) to send a request and test the response.

I know Arquillian can do this sort of thing with an embedded application server. But I don't know if there is an equivalent in the Spring world. If there is, I would very much like to know. :-)

Thank you very much.

Update

After a bit of googling, I found a pattern of using embedded Tomcat for testing like below:

// EmbeddedTomcatTestCase.java
public abstract class EmbeddedTomcatTestCase {
    private Tomcat tomcat;

    @Before
    public void setup() {
        this.tomcat = new Tomcat();
        // setup steps go here

        this.tomcat.start();
        this.tomcat.getServer().await();
    }

    @After
    public void teardown() {
        this.tomcat.stop();
    }
}

// ConcreteTest.java
public class ConcreteTest extends EmbeddedTomcatTestCase {
    // tests go here
}

One question I have about above code is that why @Before but not @BeforeClass. Isn't it expensive to start and stop a tomcat server for every test method? Another related question is, for @BeforeClass, will the tomcat server be created only once as with the EmbeddedTomcatTestCase class? That seems to be mostly a desirable effect, but are there exceptions? Most importantly, is there a simpler way than the code above? Thanks.

Upvotes: 1

Views: 4235

Answers (2)

user3151168
user3151168

Reputation:

Go with Spring MVC Test

The Spring MVC Test framework provides first class JUnit support for testing client and server-side Spring MVC code through a fluent API. Typically it loads the actual Spring configuration through the TestContext framework and always uses the DispatcherServlet to process requests thus approximating full integration tests without requiring a running Servlet container.

[...] Below is an example of a test requesting account information in JSON format:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class ExampleTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void getAccount() throws Exception {
    this.mockMvc.perform(get("/accounts/1").accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
      .andExpect(status().isOk())
      .andExpect(content().contentType("application/json"))
      .andExpect(jsonPath("$.name").value("Lee"));
    }

}

Spring MVC test does not distinguishes between components relevant to Spring MVC and your own component classes. Spring loads and configures every bean it can find in your Spring configuration, including your persistence layer classes.

Testing and persistence.xml

In a spring based application you can omit the persistence.xml in your production and test code completely.

If you can not work without a persistence.xml take a look at these exhaustive explanations done in "How to configure JPA for testing in Maven".

Otherwise take a look at my minimalistic java based configuration that works without a persistence.xml and either loads the config.properties from src/main/resources or src/test/resources depending on you are running tests or not (same is possible in xml based configuration, skipped for the sake of brevity).

AppConfig.java

package config;

import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = { "myapp" })
@EnableTransactionManagement
@Import(PersistenceJPAConfig.class)
public class AppConfig {

    //this bean has to be in the application context if you use property files
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
    }
}

JPA configuration has a separate configuration class:

PersistenceJPAConfig.java

package config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@PropertySource("classpath:config.properties")
@Configuration
@EnableTransactionManagement
public class PersistenceJPAConfig {

    @Value("${driverClass}")
    private String driverClassName;

    @Value("${url}")
    private String url;

    private boolean jpaGenerateDdl = true;

    @Value("${dialect}")
    private String hibernateDialect;

    @Value("${db.username}")
    private String username;

    @Value("${db.password}")
    private String password;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
    final LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setPackagesToScan(new String[] { "entities" }); //package wir your annotated jpa entities

    final JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter() {{
            setDatabasePlatform(hibernateDialect);
            setGenerateDdl(jpaGenerateDdl);
        }
    };
    factoryBean.setJpaVendorAdapter(vendorAdapter);

    return factoryBean;
    }

    @Bean
    public DataSource dataSource() {
    final DriverManagerDataSource dataSource = new DriverManagerDataSource();

    dataSource.setDriverClassName(driverClassName);
    dataSource.setUrl(url);
    dataSource.setUsername(username);
    dataSource.setPassword(password);

    return dataSource;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
    final JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());

    return transactionManager;
    }

}

The same is possible with Spring profiles and @ActiveProfile annotation in test classes and @Profile annotation in configuration classes or classes annotated with @Component (@Service, etc).

Upvotes: 2

Angular University
Angular University

Reputation: 43127

In order to better understand the use case, what type of tests are you running ?

As an alternative to using an embedded tomcat for integration tests, have a look at the maven cargo plugin to running a full standard tomcat at the integration testing phase, and a tool such has the chronos jmeter maven plugin to sending requests to the server and producing test report results.

This solution would start the tomcat container only once.

A full container test is usually only necessary for performance measurement tests or fully automated GUI tests like using selenium macros running from a browser, in the case of other integration tests Spring Test MVC is the best way to do it.

In case this suits the use case, this is an example of a maven configuration for a profile that starts a tomcat, runs integration tests (in this case some performance tests) and produces test reports.

This would work with any technology stack and not only Spring or Seam:

 <profiles>
        <profile>
            <id>performance-tests</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>properties-maven-plugin</artifactId>
                        <version>1.0-alpha-2</version>
                        <executions>
                            <execution>
                                <phase>initialize</phase>
                                <goals>
                                    <goal>read-project-properties</goal>
                                </goals>
                                <configuration>
                                    <files>
                                        <file>${basedir}/performance-tests/performance-tests.properties</file>
                                    </files>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.codehaus.cargo</groupId>
                        <artifactId>cargo-maven2-plugin</artifactId>
                        <configuration>
                            <container>
                                <containerId>tomcat6x</containerId>
                                <home>${basedir}/performance-tests/tomcat6-install</home>
                            </container>
                            <configuration>
                                <properties>
                                    <cargo.servlet.port>443</cargo.servlet.port>
                                    <cargo.protocol>https</cargo.protocol>
                                    <cargo.tomcat.connector.clientAuth>false</cargo.tomcat.connector.clientAuth>
                                    <cargo.tomcat.connector.sslProtocol>TLS</cargo.tomcat.connector.sslProtocol>
                                    <cargo.tomcat.connector.keystoreFile>${basedir}/tomcat/keystore.jks</cargo.tomcat.connector.keystoreFile>
                                    <cargo.tomcat.connector.keystorePass>secret</cargo.tomcat.connector.keystorePass>
                                    <cargo.tomcat.httpSecure>true</cargo.tomcat.httpSecure>
                                    <cargo.datasource.datasource.ds1>
                                        cargo.datasource.url=${perf.test.datasource.url}|
                                        cargo.datasource.driver=${perf.test.datasource.driver}|
                                        cargo.datasource.username=${perf.test.datasource.username}|
                                        cargo.datasource.password=${perf.test.datasource.password}|
                                        cargo.datasource.type=${perf.test.datasource.type}|
                                        cargo.datasource.jndi=${perf.test.ds1.jndi.name}
                                    </cargo.datasource.datasource.ds1>

                                </properties>
                            </configuration>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start-container</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>start</goal>
                                </goals>
                            </execution>
                            <execution>
                                <id>stop-container</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>stop</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>chronos-jmeter-maven-plugin</artifactId>
                        <version>1.1.0</version>
                        <configuration>
                            <input>${basedir}/performance-tests/mobile-gateway-performance-tests.jmx</input>
                            <jmeterhome>${basedir}/performance-tests/jmeter</jmeterhome>
                        </configuration>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>jmeter</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <reporting>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>chronos-report-maven-plugin</artifactId>
                        <version>1.1.0</version>
                    </plugin>
                </plugins>
            </reporting>
        </profile>
    </profiles>

Upvotes: 1

Related Questions