Skip to main content

Again writing fast & maintainable integration tests using in-memory db, liquibase, gradle and spring mvc test

This blog is improvisation to previously blogged testing effectively with database blog. In this blog we will remove the jetty setup and instead use Spring MVC Test framework which kind of simulate the request/response for your controllers via Spring Web Application context (no deployment, to http client tests) Here, we will write Spring MVC tests and -
  • Create the HSQLDB in-memory instance (instantiating datasource instance)
  • and run Liquibase scripts against them. (using Spring Lqiuibase bean)
1. Setup your web-service.

Write usual Controller->Service->Repository layer.

@Controller
@RequestMapping("/reports")
public class MapAnomalyReportEndpoint {

    @Autowired
    private MapAnomalyReportService reportService;

    @ResponseBody
    @RequestMapping(method = RequestMethod.GET)
    public Collection getReports() {
        return reportService.getReports();
    }

}
2. Create Spring Java Config (No XML)

Configure the controllers.

@Configuration
@EnableWebMvc
@Import(JdbcConfig.class)
@ComponentScan(basePackages = "com.rohankar.playground")
public class WebConfig {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        final InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}
Configure the data source. I'm taking JDBC parameters from context, you can very well read them from .properties or system prop.
@Configuration
public class JdbcConfig {

    @Autowired
    private Environment env;

    @Bean(destroyMethod = "close")
    public DataSource getDataSource() throws NamingException {
        final Context ctx = new InitialContext();
        final BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName((String)ctx.lookup("java:comp/env/report/db/driverClass"));
        ds.setUrl((String)ctx.lookup("java:comp/env/report/db/url"));
        ds.setUsername((String)ctx.lookup("java:comp/env/report/db/user"));
        ds.setPassword((String)ctx.lookup("java:comp/env/report/db/password"));
        return ds;
    }

    @Bean
    public JdbcTemplate getJdbcTemplate() throws NamingException {
        return new JdbcTemplate(getDataSource());
    }

}
3. Create Spring Java Config for tests

This config is purely for running liquibase scripts on datasource.

@Configuration
@Import(WebConfig.class)
public class TestConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public SpringLiquibase getLiquibase() {
        final SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog("classpath:setup-db.xml");
        return liquibase;
    }
}
4. Write integration test In setUp method, we're populating a context object with in-memory db parameters.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {TestConfig.class})
public class MapAnomalyReportEndpointIntTest {

    @Autowired
    private WebApplicationContext context;

    @BeforeClass
    public static void setup() throws IllegalStateException, NamingException {
        final SimpleNamingContextBuilder context = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
        context.bind("java:comp/env/report/db/driverClass", "org.hsqldb.jdbc.JDBCDriver");
        context.bind("java:comp/env/report/db/url", "jdbc:hsqldb:mem:reportdb;shutdown=false");
        context.bind("java:comp/env/report/db/user", "sa");
        context.bind("java:comp/env/report/db/password", "");
        context.bind("java:comp/env/report/db/schema", "report");
        context.activate();
    }

    @Test
    public void testGetReports() throws Exception {
        MockMvcBuilders.webAppContextSetup(context).build() //
            .perform(get("/reports")) //
            .andDo(print()) //
            .andExpect(status().isOk()) //
            .andExpect(content().contentType("application/json;charset=UTF-8")) //
            .andExpect(jsonPath("$[0].id").value(1)); // TODO assert other values
    }
}
5. Run the tests, this wont require you to configure jetty, deploy the ws and run test against them!

You can clone the whole project from here.

PS: The given project is built using Gradle as I want to learn it.

Comments

Popular posts from this blog

Installing i3lock-color on Ubuntu

i3lock is fancy lock screen for Linux. There is i3lock dep available on Ubuntu but its not what I was looking for; I was more interested in i3lock-color . Lets see how we can install the same on ubuntu. PS: I'm using Ubuntu 16.04 Get source code for i3lock-color $ git clone https://github.com/PandorasFox/i3lock-color.git  $ cd i3lock-color Install required packages to build i3lock  $ apt install libev-dev $ apt install libxcb-composite0 libxcb-composite0-dev libxcb-xinerama0 libxcb-randr0  $ apt install libxcb-xinerama0-dev  $ apt install libxcb-xkb-dev  $ apt install libxcb-image0-dev  $ apt install libxcb-util-dev $ apt install libxkbcommon-x11-dev $ apt install libjpeg-turbo8-dev  $ apt install libpam0g-dev Build  $ autoreconf -i && ./configure && make Execute $ ./lock.sh Assign Shortcut In order to assign shortcut, install compizconfig $ sudo apt-get install compizconfig-settings-manager compiz-plugins-extra and then go

INTO OUTFILE query, How to modify & output null values ?

I tried one query (originally asked at JR) on MySQL which export the table data into a file, with a comma delimited field. The query is straight forward and replace the null column value by "\N" character, but when I want to replace that "\N" value by some other character like "0" or simple empty field like " ", its gets little tricky. Here is the base query SELECT Id, name, age FROM student INTO OUTFILE 'c:/result.txt' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n'; And it outputs like, 1, "sagar", 23 2, "anant", 24 Now suppose some 'name' fields are null, then it outputs like 1, "sagar", 23 2, \N, 24 Now to obtain my desired results, which replace this null (\N) values by empty string like, "", I tried out two solutions --1. Using CASE statement SELECT Id, CASE WHEN name IS NULL THEN '' ELSE name END AS NewName, age FROM s

Java Generics: Why I get "name clash" exception when I override methods with different "type" parameter ?

import java.util.*; class Parent { void foo(List<String> list) { System.out.println("parent"); } } class Child extends Parent { void foo(List<Integer> list) { System.out.println("child"); } } When you compile this code you will get an error like Parent.java:7: name clash: foo(java.util.List<java.lang.Integer>) in Child and foo(java.util.List<java.lang.String>) in Parent have the same erasure, yet neither overrides the other class Child extends Parent { ^ 1 error This "name clash" error thrown because of "type erasure" thing when "generics" code gets complied. Let’s see how, When we complied any generics code, the compiler removes the type from the code and treat it as a simple pre Java 1.5 code, like, in our case, compiler saw something like this, import java.util.*; class Parent { void foo(List list) { // oops, <String> get erased. System.out.println("parent"); } } class Child extend