Saturday, February 22, 2014

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.

Thursday, June 13, 2013

Writing fast & maintainable integration tests using test double, in-memory db, jetty, maven and spring

Writing an integration test which test end to end flow of your application can become cumbersome activity if you really hitting real db and services. The best way to mock, test double your external dependencies and thus speed up your test execution.

I created a dummy map-service project which demonstrate about writing maintainable and speedy integration tests using mocking, test double pattern, spring and automating them using maven and jetty. You can find the project at my github location: https://github.com/sagarr/maps-service

Here are some code snippets explaining the important steps in configuring your project.

  1. POM - Configure jetty for deploying your app and running tests against it.
  2. .
    
    
     org.mortbay.jetty
     jetty-maven-plugin
     ${jettyVersion}
     
      
       /${project.artifactId}
      
      8005
      STOP
      5
      true
      
       
        8080
        60000
       
      
     
     
      
       start-jetty
       pre-integration-test
       
        run
       
       
        0
        true
       
      
      
       stop-jetty
       post-integration-test
       
        stop
       
      
     
    
    
    
    
     maven-failsafe-plugin
     2.12
     
      
       **/integration/**Test.java
      
     
     
      
       integration-test
      
     
    
    .
    .
    
  3. MockBeanInjector.java - In order to test double database and service calls
  4. public class MockBeanInjector implements BeanPostProcessor {
    
        private final static Logger LOG = LoggerFactory.getLogger(MockBeanInjector.class);
    
        @Override
        public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
            if (bean instanceof DataSource) {
                LOG.info("Mocking DataSource instance: " + bean);
                final EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
                final EmbeddedDatabase embeddedDatabase =
                    embeddedDatabaseBuilder.addScript("schema.sql").addScript("data.sql").build();
                return embeddedDatabase;
                // else
                // return new ClassPathXmlApplicationContext("applicationContext-test.xml").getBean("dataSource");
            }
            if (bean instanceof AuthserverGateway) {
                LOG.info("Mocking AuthserverGateway instance: " + bean);
                final AuthserverGateway mockAsGateway = mock(AuthserverGateway.class);
                final ResponseEntity response = new ResponseEntity(HttpStatus.NO_CONTENT);
                when(mockAsGateway.callAuthorizeService("12345")).thenReturn(response);
                return mockAsGateway;
            } else {
                return bean;
            }
        }
    }
    
  5. Finally tests
  6. public class PoiWsRestClientTest {
    
        @Test
        public void should_create_poi() {
            // given
            final Poi poi = new Poi();
            poi.setName("Pizza Center");
            poi.setLatitude(new BigDecimal("23.23212"));
            poi.setLongitude(new BigDecimal("34.231312"));
    
            // when
            final URI location = new RestTemplate().postForLocation("http://localhost:8080/maps-service/pois", poi);
    
            // then
            assertThat(location.getPath(), endsWith("/pois/102"));
        }
    
        @Test
        public void should_read_poi() {
            // when
            final Poi poi = new RestTemplate().getForObject("http://localhost:8080/maps-service/pois/100", Poi.class);
    
            // then
            assertThat(poi.getId(), is("100"));
        }
    
        @Test
        public void should_delete_poi() {
            // given
            final HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", "12345");
            final HttpEntity httpEntity = new HttpEntity(headers);
    
            // when
            final ResponseEntity response =
                new RestTemplate().exchange("http://localhost:8080/maps-service/pois/101", HttpMethod.DELETE, httpEntity,
                    String.class);
    
            // then
            assertThat(response.getStatusCode(), is(HttpStatus.NO_CONTENT));
        }
    }
    

Wednesday, June 12, 2013

Code Kata - good way to learn new language

Recently, I started exploring and learning Scala and functional programming. So far, it is a good experience and as a programmer gives you new "functional" thought process about problem.

The best way to learn Scala or any language for that matter is to start writing Katas in that language. Coding Kata, if you're not familiar with it then must visit codingdojo.org. In short, coding kata is programming practice mostly done in TDD way, which makes you better programmer.

BTW, If you're wondering where are my katas in Scala, you can find them here: https://github.com/sagarr/scala-dojo

Monday, April 8, 2013

A Day of Innovation: TomTom Pune DevDay 2013

Friday (6 April 2013) was a day of innovation and engineering, fun and quiz, it was a first TomTom Pune Engineering DevDay.

Key Highlights of the day:

  • Kick started with U2 public concert video, a good way to wakeup people.
  • Many speaker talked about how we can extend and experiment with our new generation map editing tool Cartopia
  • I talked about function programming and how we can achieve it using Scala.
  • There were coffee breaks with testers which showcase some really cool technologies like puppet, test automation using FlexPilot and risk based testing's.
  • Day end with exciting quiz, prize distribution and beer :)

If you want to be a part of next TomTom devday, join us http://www.tomtom.jobs

Friday, December 7, 2012

Adjustable flex datagrid row height for multi-level tree component

Note: This blog is extension to the post blogged here, the OP has used one level tree to display in grid, whereas I used multilevel tree.

From last 2-3 months I'm working on Flex, developing our in-house map editor. We've a requirement to show a grid with some column containing multilevel tree data, I googled it and found the above link which was matching to our requirement but got one problem, it didn't work correctly for multilevel tree. The problem is -

  • It failed to adjust height when you expand inner child element.
  • The whole tree get collapsed when you collapse the inner element.

To provide a solution to above problems, you have to -

  • Keep a flag which tells if root is closing or not, if root is closing keep the original height.
  • For all other child expansion, count the children from existing "open items" and adjust height accordingly.

The below code explains it all.


    
        
  


Monday, August 20, 2012

Tests your logs using logback

You want to test what your application logging and you don't know how to do that in simplest manner, well we've logback to rescue. (If your using slf4j but with other than logback as a implementation, then you have to add logback as a test dependency with logback.xml in src/test/resources)

One (not exactly) caveat is that, for testing logs using logback we need to use Mockito for creating mock Appnder and asserting for the exact log string in argument.

Here is the actual code-

1. Create a base test class for log testing.
public abstract class BaseLoggerTest {

    final Appender mockAppender = mock(Appender.class);

    @Before
    public void setup() {
        when(mockAppender.getName()).thenReturn("MOCK");
        (ch.qos.logback.classic.Logger)LoggerFactory
        .getLogger(ACCESS_LOGGER_NAME))
        .addAppender(mockAppender);
    }

    protected void verifyLog(final String expectedLog, final Level level) {
        verify(mockAppender, atLeastOnce())
        .doAppend(Matchers.argThat(new ArgumentMatcher() {

            @Override
            public boolean matches(final Object argument) {
                return ((LoggingEvent)argument).getLevel().equals(level)
                    && ((LoggingEvent)argument).getFormattedMessage()
                       .contains(expectedLog);
            }
        }));
    }
}
2. Extend logger base class to verify actual logging
public class MyClassLoggerTest extends BaseLoggerTest {

    @Test
    public void testLogSomethingShouldLogSomething() throws IOException {
        // given
        MyClass object = new MyClass();

        // when
        object.logSomething();

        // then
        verifyLog("I just logged something.", Level.INFO);
    }
}
Simple.

Wednesday, June 27, 2012

Good Vs Better Programmer

We always have some good programmers around us who are capable of doing work at hand, but to success in long term we need better programmers.
Here are some traits I found important to become better programmer.

  1. Good programmer start coding right away after picking up the task, better programmer first do some little design upfront (may be draw some UML, flowcharts etc), write the simplest readable tests first and then write minimum production code which makes that test pass.
  2. Good programmer stick to the tool, framework, workflow he/she knows best, better programmer keep the affinity towards any technology aside and evaluate and use the best possible tool, framework available. 
  3. Good programmer focuses on working code, better programmer too focused on working code but also gives the utmost importance to code readability, flexibility and other design principles.
  4. Good programmer ever read technology books when he/she was in college, better programmer subscribed to feeds, question/answers on technology forum and sometimes blogs.
  5. Better programmers embrace the test pyramid and write unit, integration and UI/acceptance tests in order to flag given task as delivered.
You can certainly guess that better programmers are no one but Agile Programmer. The list is not limited to this only. There are other stuff you want to read to learn more about better programming, like 97 Things Every Programmer Should Know
Update: I came across DZ article which speaks what I was trying to cover here.