Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

Entries in spring (12)

Wednesday
Dec192012

The future of Web MVC testing

Ok, ok, I'm excited. Right now I just was able to hack my way through a test with Spring 3.2 and the new test web context.

The method I ended up writing looked like this:

@Test
public void tryStartQuiz() throws Exception {
   this.mvc.perform(get("/engine/start/james"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(jsonPath("$.quiz_id").exists());
}

How did I get here?

I upgraded to Spring 3.2, which has native support for loading Web contexts (well, not in a separate-but-parent/child way where you can mount the web context and business context separately, that's coming, but in one context).

Here is the top of my test class:


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
   "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml", 
   "classpath:/META-INF/spring/applicationContext.xml"})
public class EngineControllerTest {

    @Autowired
    WebApplicationContext context;

    MockMvc mvc;

    @Before
    public void setUp() {
        mvc = webAppContextSetup(this.context).build();
    }

    ...

Some analysis...

The MockMvc object, which I referred to as this.mvc in my test, allows me to mock up the web container request and record expectations. It is created in the @Before method, after Spring injects a test WebApplicationContext object, which is driven via the @WebAppConfiguration above.

The WebApplicationContext object is a web app engine in a test engine. It boots a Spring MVC platform, mocking the network engine so that you can submit requests with calls like mvc.perform(get(...) ... and chain assertions, using .andExpect statements to build conditions. It's like headless Spring MVC. This allows you to test by submitting URLs but without having to configure Jetty or Tomcat in a complex server bootstrapped integration test.

But where is it looking for my webapp?

As a nice touch, the default path for the web application is src/main/webapp. If you're not mavenized, or have a test version of your webapp you want to use, you can submit the path to it as the value of the annotation, like:

@WebApplicationContext("src/test/lightweightwebapp")

Assertions and matchers

There are several static imports that make life easier, as outlined in this great slide show from the MVC team (Rossen and friends):

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

I had to hack a bit with the commands and don't have the DSL figured out much so far. But it is nice to test through the request dispatcher rather than inject a controller directly. It's more natural and lets you really test the way production will handle your requests/responses.

What about more sophisticated checks?

The expect syntax doesn't really allow dicing and slicing of the response as easily as grabbing it in a variable. You can tell the mock MVC object to return the result, which contains the MockHttpServletResponse. In this example, I'm using FlexJson (from Roo's dependencies) to deserialize the JSON request into a HashMap of string keys and values, and assert that the data exists in the proper format.

@Test
public void tryStartQuiz() throws Exception {
	JSONDeserializer<HashMap<String, String>> deserializer
			= new JSONDeserializer<HashMap<String, String>>();

	MvcResult result = this.mvc.perform(get("/engine/start/james"))
			.andExpect(status().isOk())
			.andExpect(content().contentType(MediaType.APPLICATION_JSON))
			.andExpect(jsonPath("$.quiz_id").exists())
			.andReturn();

	Map<String, String> jsonPayload =
			deserializer.deserialize(
					result.getResponse().getContentAsString());

	assertThat(jsonPayload.containsKey("quiz_id"), is(true));
	assertThat(jsonPayload.get("nickname"), is("james"));

	System.err.println(jsonPayload);


}

Roo and Spring 3.2

One more thing - I came from Roo 1.2.2, and as I upgraded the spring.version property to Spring 3.2 (yes, it works just fine) so I had to add one dependency in order for this to work, test scoped:

<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>

Roo was just upgraded to version 1.2.3 this week (check it out here). I am hoping it uses Spring 3.2 out of the box.

This test will be checked into the next update of Quizzo-Angular-Roo - my current obsession which is using Spring Roo on the backend, and Angular.JS on the front-end, for a single-page JS quiz engine.

Where to go to view the code?

Today I've pushed this code (somewhat modified) along with working Jasmine tests, AND a working Maven test suite that includes automatic Jasmine testing every time I run mvn test. Check it out at quizzo-angular-roo on GitHub. I warn you, it will be primitive and so far not really doing much, but it will shape up over time.

Sunday
Aug292010

Spring JUnit Tests not Rolling Back? It may not be you...

Plug/Disclaimer! I'm teaching a Hibernate with Spring course in September, and while preparing for the course I came up with this tidbit. I hope you enjoy it.

Here's a little tip for you Spring users who are using MySQL.  If you just installed MySQL with the defaults, you may find that Spring's @ContextConfiguration and @RunWith(SpringJUnit4Runner.class) annotations might not work for you.

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.
       transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@ContextConfiguration(locations=
   {"classpath:/META-INF/spring/applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration(defaultRollback=true)
public class CourseIntegrationTest {

  @Autowired
  private SessionFactory sessionFactory;
  
  
  @Test
  @Transactional
  public void testCreateCourse() {
    Session session = sessionFactory.getCurrentSession();
    Course course = new Course();
    course.setCost(new BigDecimal("1000.00"));
    course.setDescription("Basketweaving");
    course.setStartDate(new Date());
    
    session.save(course);
    
    // now, we get and check
    session.flush();
    
    Assert.assertNotNull(course.getId());   
    
  }
  
}

The Problem...

The default behavior of Spring when running integration tests like this is to roll back the transaction. So, you go ahead and execute the test, and hope that it rolls back the row. But, in fact, it might not - you might see the row in the database. Why?

The answer lies in whether you've installed and configured the InnoDB engine in MySQL. What is InnoDB? It's a transactional storage engine that ships with MySQL binaries as of 5.1 and higher. Here is a good wikipedia article on InnoDB for further reading. You can tell whether it is installed by executing the following SQL as the 'root' MySQL user: (I've removed the "comment" field so it fits on my blog page)


mysql> show engines;
+------------+---------+--------------+------+------------+
| Engine     | Support | Transactions | XA   | Savepoints |
+------------+---------+----------------------------------+
| CSV        | YES     | NO           | NO   | NO         |
| MRG_MYISAM | YES     | NO           | NO   | NO         |
| MEMORY     | YES     | NO           | NO   | NO         |
| MyISAM     | DEFAULT | NO           | NO   | NO         |
+------------+---------+--------------+------+------------+

In the case above, I haven't yet configured InnoDB - MyISAM is the default engine, which is also non-transactional. Since MySQL can have several installed engines, and one is the default, setting the wrong default (as well as not installing a transactional engine) can be a problem!

When you create tables, you can specify the engine they use, otherwise they get the default. I found a GREAT article about verifying your Spring JPA MySQL tables to make sure they use a transactional (InnoDB) data store. Since we're geeking out, you can also run this command in MySQL against your table to see what settings it has (many more columns come back than the ones I'm showing):

mysql> show table status;
+--------+--------+---------+------------+------+----------------+
| Name   | Engine | Version | Row_format | Rows | Avg_row_length |
+--------+--------+---------+------------+------+----------------+
| Course | MyISAM |      10 | Compact    |    1 |          16384 |
+--------+--------+---------+------------+------+----------------+

Run your JUnit Spring integration tests against a table with this engine, and you'll see that rollbacks are ignored, even though Spring shows that they are sent. Here is what Spring shows us when we run the test, which would lead you to believe that everything is ok, until you look at the data in the table:

Fetching JDBC Connection from DataSource
Returning JDBC Connection to DataSource
Creating new transaction with name [testCreateCourse]: 
  PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
Opened new Session ... for Hibernate transaction
Preparing JDBC Connection of Hibernate Session ...
Exposing Hibernate transaction as JDBC transaction 
   [jdbc:mysql://localhost:3306/hibernate_sandbox, 
   UserName=root@localhost, MySQL-AB JDBC Driver]
Hibernate: 
    insert 
    into
        Course
        (cost, description, startDate) 
    values
        (?, ?, ?)
binding '1000.00' to parameter: 1
binding 'Basketweaving' to parameter: 2
binding '29 August 2010' to parameter: 3
Triggering beforeCompletion synchronization
Initiating transaction rollback
Rolling back Hibernate transaction on Session ...
Triggering afterCompletion synchronization
Closing Hibernate Session ... after transaction
Closing Hibernate Session
Closing Hibernate SessionFactory

Incidentally, here is my log4j.properties file for getting all of that nice log output:

# suppress everything else
log4j.logger.org.springframework=error
log4j.logger.org.hibernate=error

# log field bindings
log4j.logger.org.hibernate.type=trace

# log transactions
log4j.logger.org.springframework.jdbc.datasource=trace
log4j.logger.org.springframework.orm.hibernate3=trace 

... and my Hibernate settings from within my AnnotationSessionFactoryBean...

<property name="hibernateProperties">
  <value>
    hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
    hibernate.hbm2ddl.auto=update
    hibernate.show_sql=true
    hibernate.format_sql=true
  </value>
</property>

Ok, so obviously for a serious application involving more than one SQL statement at a time, this is seriously inadequate. So, let's fix it!

Installing InnoDB

I'm using a Mac, so your mileage for these instructions will vary, and you'll have to have a passing familiarity with the command line. First, create a my.cnf file (or edit the existing one). Mine is located in /etc/my.cnf, but yours may live in /usr/local/mysql/data or in another place. I have added the following settings to my file, taken from a few blog entries:

[mysqld]
default-storage-engine=InnoDB
innodb_data_home_dir=/usr/local/mysql/data
innodb_data_file_path=ibdata-new:10M:autoextend
innodb_buffer_pool_size=256M
innodb_additional_mem_pool_size=20M
innodb_log_file_size=64M
innodb_log_buffer_size=8M
innodb_flush_log_at_trx_commit=1

Now, to install this file, you need to shutdown and start up MySQL. I use the following commands from OS X:

sudo mysqladmin shutdown
sudo mysqld_safe --console & 
sudo cat /usr/local/mysql/data/yourservername.err

Verifying the Installation

Now, to verify that everything is configured correctly, check the same

show engines
command again as the MySQL root user:

mysql> show engines;
+------------+---------+--------------+------+------------+
| Engine     | Support | Transactions | XA   | Savepoints |
+------------+---------+--------------+------+------------+
| CSV        | YES     | NO           | NO   | NO         |
| MRG_MYISAM | YES     | NO           | NO   | NO         |
| MEMORY     | YES     | NO           | NO   | NO         |
| InnoDB     | DEFAULT | YES          | YES  | YES        |
| MyISAM     | YES     | NO           | NO   | NO         |
+------------+---------+--------------+------+------------+
5 rows in set (0.01 sec)

If all is well, you now have InnoDB, and it's the default engine. Try the test, and see if the rows are rolled back. Important: you may have to drop or modify the table to make it use InnoDB. There is a simple SQL command to modify it:


mysql> alter table Course engine=InnoDB;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

If you get any warnings, just type

show warnings
and you'll get a message.

That's it. I hope this helps someone who is wrestling with MySQL databases and Hibernate transactions. I know I have had trouble with this when preparing the Hibernate section of my book, Roo in Action.

Thursday
Jul012010

RESTing with Roo - adding Content Negotiation and REST in two easy steps (well, kinda)

Note: I don't know if I'm approaching this right... At least on Firefox, it seems to work. But on Safari, it fails. I'll revisit this soon.

Ok, here's another stream-of-consciousness post on REST support from a Roo application. I'm working on various research for our [PLUG] SpringSource Core Spring training classes, always attempting to get better examples so I can do more than just show the slides and labs, and I decided to use Roo to build myself a REST server.

Easy, I thought... Roo has RESTful URLs, and so I just have to set up the right configuration.

Kind of.

The basic steps to REST-enable a web app in Spring MVC using the built-in REST support are:

  1. Install the proper JARs for your Marshallers
  2. Install the ContentNegotiatingViewResolver

I had a bit of a hiccup figuring this out. Thanks to a great pair of threads on SpringSource's forums, here and here, I realized I had configured a few things wrong.

Setting up the example

Here's my Spring Roo log file, which you can cut and paste into a text file and then do a "roo script filename" command to set up for you.

project --topLevelPackage rest.demo --projectName spring-mvc-rest-demo
persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT 
entity --class ~.db.Customer
field string firstname
field string lastname
field date --fieldName dob --type java.util.Date
field number --fieldName discount --type java.math.BigDecimal --decimalMin 0 --decimalMax 1.0
controller scaffold --entity rest.demo.db.Customer --class rest.demo.web.CustomerController

Once you do that, now you're ready for some fun...

The additional dependencies for your Maven pom.xml file (you'll need to add these to the main dependencies tag section at the end...) (for XML conversion, we're using Castor, which auto-generates XML based on the reflection of JavaBean properties on your bean, and for JSON we're using the Jaskson JSON library. For this to work, you have to install spring-oxm, which isn't installed in Roo by default - I SMELL PLUGIN... RACE YA!).

<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.5.3</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-oxm</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.codehaus.castor</groupId>
  <artifactId>castor-xml</artifactId>
  <version>1.3.1</version>
</dependency>

Now, the Spring webmvc-config.xml file changes. This file is located in src/main/webapp, under the WEB-INF/spring directory, of course. Add this to the end of the file before the closing beans tag...

<bean id="htmlMediaType" class="org.springframework.http.MediaType">
  <constructor-arg value="text" />
  <constructor-arg value="html" />
</bean>
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="0" />
  <property name="defaultContentType">
    <ref bean="htmlMediaType" />
  </property>
  <property name="mediaTypes">
    <map>
      <entry key="json" value="application/json" />
      <entry key="xml" value="application/xml" />
    </map>
  </property>
  <property name="defaultViews">
    <list>
      <bean
     class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
      <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
        <property name="marshaller">
          <bean class="org.springframework.oxm.castor.CastorMarshaller" />
        </property>
      </bean>
    </list>
  </property>

  <property name="viewResolvers">
    <ref bean="tilesViewResolver" />
  </property>
</bean>

Next, in the same file, add the order property, setting it to 1, to the instance of the AjaxUrlBasedViewResolver so that it looks roughly like this :

<bean class="org.springframework.js.ajax.AjaxUrlBasedViewResolver"
  id="tilesViewResolver">
  <property name="order" value="1" />
  <property name="viewClass"
    value="org.springframework.web.servlet.view.tiles2.TilesView" />
</bean>

What you're seeing at work here in this setup is the configuration of a default media type of text/html, and a configuration of a first-level view resolver that does content negotiation first. You'll see the order setting was made to make the AjaxBasedViewResolver go first, which threw me, but that's an area for more research as I start to dig deeper into the interplay between the various view resolvers... Generally you want the JSP file-based resource view resolver to go last, as it is greedy, but Tiles changes that for us a bit. I am not 100% sure everything works as advertised - for example I need to do some AJAX testing to make sure those calls work. But for getting REST against a webapp, well, there you have it.

Testing it!

Well, that wasn't so much configuration, was it? I'm experimenting with REST using the Groovy RESTClient library at the moment (I'm going to use Spring REST Template in JUnit4 for the class, but also wanted something I could script up at a moment's notice and show them how REST works without waiting for those compile cycles...) BTW, I'm on Groovy 1.7.2 (haven't yet upgraded to 1.7.3) and the @Grab line uses Groovy's GRAPE artifact manager to install a grape from the ole' vine (puts it in ~/.groovy/grape/grapes or something)... WICKED COOL. Yes, I used my blog to say WICKED COOL. You can't stop me!

@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.0' )
import groovyx.net.http.RESTClient

def customers = new RESTClient( 'http://localhost:8008/demo/')
def results = customers.get (path : 'customers',
   headers: [Accept : 'application/json', "Accept-Encoding" : 'gzip,deflate'])

println results.data

Switch from JSON to XML, using application/xml as the Accept header, and you'll see it automatically respond with the appropriate datatype. Great! Now, if you use XML, change from that println statement to this:

results.data.customer.each { c ->
  println "customer:  $c.id, $c.firstname $c.lastname"
}

And if you want to test it on the browser, and see it do content negotation by file extension instead, just hit: http://localhost:8080/spring-mvc-rest-demo/customers.xml or http://localhost:8080/spring-mvc-rest-demo/customers.json to see the REST data, and http://localhost:8080/spring-mvc-rest-demo/customers to see the regular tiles views.

Enjoy...

Oh, and while I'm shilling for my training group at Chariot, please visit our course calendar if you are interested in our fall lineup - Hibernate with Spring, Spring Enterprise Integration, Core Spring, or Maven Intro and Advanced training. Thanks, and I'll refrain from using a strange chamois in any of my in-line advertisements...

Thursday
Nov192009

Philly Spring User Group Meeting Tonight 11/19

The meeting is being held downtown at 2001 Market Street, and will feature SpringSource's Tom McCutch.  He will be talking about Spring 3.0, and giving a demonistration of the developer version of SpringSource's tc Server, an optimized and instrumented version of Tomcat.

Details : http://phillyspring.ning.com/events/november-meeting-spring

Tuesday
Jun022009

Gettin' all Testy - The Spring TestContext, Hibernate, and you...

I'm getting some materials ready around a project using the Hibernate Annotations API in Spring 2.5.6.  The client is using Hibernate rather than straight JPA, likely due to wanting to get features such as Filters, Criteria, etc...

I've been used to JUnit 3.x for a while, but as I'm testing my Hibernate knowledge, I came across the newer Spring Test Context framework.  If you install JUnit 4.4 (I'm using Maven, so I just added it as a test-time dependency) and also the spring test-context library, you'll have the ability to define tests that ramp up a Spring Context using the @ContextConfiguration annotation and coupled with extending a specific Spring abstract class, AbstractJUnit4SpringContextTests.

This is similar to the Junit 3.8 context configuration, but uses JUnit 4 semantics.  Also you can use TestNG, just use a different abstract class.  Here is a fragment:

@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class HibernateModelTest 
   extends AbstractJUnit4SpringContextTests {
...
}

 

Ok, that's cool.  You get an applicationContext member in the test class too. So, what if you wanted to interact with the Hibernate API directly?  I've mounted my Hibernate configuration using the Spring Hibernate AnnotationSessionFactoryBean, scanning for entities in the org.rimple.example.domain package as in this example:

<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan" value="org.rimple.example.domain"/>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.HSQLDialect
        hibernate.hbm2ddl.auto=create
        hibernate.cache.provider_class=org.hibernate.cache.NoCacheProvider
        hibernate.show_sql=true
        hibernate.current_session_context_class=thread
        hibernate.connection.pool_size=15
      </value>
    </property>
</bean>
       

Now I can just create a @Before and @After method to setup and tear down the session context.  I can get fancy and begin a transaction and end it, just like the transactional tests provided for Repository tests, but then I can open and close and interact with the session myself.

    private Session session;
    @Before
    public void setupSession() {
        SessionFactory sessionFactory = (SessionFactory) applicationContext.
getBean("mySessionFactory");
        session = sessionFactory.openSession();
        session.getTransaction().begin();
    }

    @After
    public void tearDownSession() {
        session.getTransaction().rollback();
        session.close();
    }

Now, all I have to do is just interact with Hibernate directly. Given this Entity:


@Entity
@Table(name="employees")
public class Employee {

    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @Column(nullable=false, length=30)
    private String firstName;

    @Column(nullable=false, length=30)
    private String lastName;

    @Column(nullable=false)
    private Boolean active;

    @Column(nullable=false)
    @Temporal(TemporalType.DATE)
    private Date hireDate;

    @Column(nullable=true)
    private String comments;
    ...
}

We can use this test:

    @Test
    public void createEmployee() {
        Employee e = new Employee();
        e.setActive(true);
        e.setFirstName("Joe");
        e.setLastName("Smith");
        e.setComments("A new employee");
        e.setHireDate(new Date());

        session.save(e);
        assertNotNull(e);
    }

QED!  I am partial to the JPA Entity Manager and putting Hibernate behind a facade, but this works if your team decides they are using the Hibernate annotation and API instead.