Testing Spring MVC with MockMVC

Testing Spring controllers can be interesting to test. Before MockMVC existed, the options were limited to:

  • Instantiating a copy of the controller class, injected the dependencies (possibly with mocks) and calling the methods by hand.
  • Firing up the container and making HTTP calls by hand
  • Using something like selenium to automated the HTTP calls.

There are a number of disadvantages to each of these approaches. Checking the resulting HTML can require a lot of code and can be quite brittle when the UI changes. And while the direct controller approach is fairly lightweight, it doesn’t test request mapping, or any of the automatic variable conversion code. This is where MockMVC comes in, and allows testing of a large part of the MVC framework using a fluent API. 

One important thing to note is that MockMVC only supports testing the Model and Controller parts of MVC. It does not test the view transformation. 

The process for using MockMVC is setting up a MockMVC object:

[sourcecode lang=”java”]@Transactional
@RunWith(SpringRunner.class)
@SpringBootTest()
@ActiveProfiles(“test”)
public class PushControllerTest {

@Autowired
private WebApplicationContext context;

private MockMvc mvc;

@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}

.
.
.

}[/sourcecode]Once we have the MockMVC object, we can start building a request and then test expectations on the response.

[sourcecode lang=”java5″]@Test
public void testIndex() throws Exception {
mvc.perform(get(“/index.do”))
.andExpect(status().isOk());
}[/sourcecode]This simple test checks that a GET request to /index.do returns a 2xx response. 

Tests can get much more complicated, and additional tests can be be performed, for example checking database state.

[sourcecode lang=”java5″]@Test
public void testSubmitForm() throws Exception {

assertThat(taskManager.getAll(), hasSize(0));

mvc.perform(post(“/submit.do”)
.param(“entity”, “ASSET”)
.param(“externalId”, “12345”))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().hasNoErrors())
.andExpect(model().size(2))
.andExpect(model().attributeExists(“form”))
.andExpect(model().attributeExists(“entities”))
.andExpect(model().attribute(“entities”, hasItem(EntityName.EDITION)))
.andExpect(model().attribute(“entities”, hasSize(EntityName.values().length)));

Task task = taskManager.getAll().get(0);

assertThat(task, notNullValue());
assertThat(task.getType().getName(), is(“asset”));
assertThat(task.getUrl(), is(“http://www.example.com/dam.aspx?p=xmlgetdata&entity=asset&id=12345”));

}[/sourcecode]

Creating requests

Note: In this section, examples only show partial examples with the perform() function and not the andExpect() checks.

The perform() function takes a RequestBuilder object. org.springframework.test.web.servlet.request.MockMvcRequestBuilders has a number of static methods you can use to make this easy. In the examples, we statically import the function, so that the code reads more fluently. They are:

  • get() 
  • post()
  • put()
  • patch()
  • delete()
  • options()
  • head()
  • fileUpload()

There’s also a generic request() method if you need more control, for example if you needed a TRACE request. Each method comes in two forms: one that takes a url String, and a second that takes a url template and a varargs list of parameters. The template uses names surrounded by braces, but the names are not used and parameters are substituted based on the order in the template and value list. The following two examples are identical.

[sourcecode lang=”java5″]mvc.perform(get(“/index.do?id=1”))
mvc.perform(get(“/index.do?id={id}”), 1)[/sourcecode]

Customising the Request

The returned request builder provides a great deal of options to customise the request for the test. 

Secure requests

If your code depends on running under HTTPS, you can set the request secure flag. 

[sourcecode lang=”java5″]mvc.perform(get(“/index.do?id=1”).secure(true))[/sourcecode]

Request body

When testing PUSH or PUT requests, you often need to set the body of the request. You can use either a String or a byte array.

[sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”).content(jsonBody))[/sourcecode]

Content Type

When setting the body content, you probably want to set the content type too.
[sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”)
.content(jsonBody)
.contentType(MediaType.APPLICATION_JSON))[/sourcecode]You can either use a MediaType constant or a media type String. 
[sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”)
.content(jsonBody)
.contentType(“application/json”))[/sourcecode]

Character Encoding

Setting the character encoding is as easy as passing the encoding as a string. The default will not include a charset parameter in the Content-Type header. HTTP 1.1 defaults to ISO-8859-1 in that situation.

[sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”)
.content(jsonBody)
.characterEncoding(“utf-8”))[/sourcecode]

Accept header

You can set the content types that the request will accept by either passing a vararg of MediaType objects or Strings.

[sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”)
.content(jsonBody)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML))[/sourcecode][sourcecode lang=”java5″]String jsonBody = “{ color: “red”,value: “#f00″}”;
mvc.perform(post(“/color/1”)
.content(jsonBody)
.accept(“application/json”, “application/xml”))[/sourcecode]

Cookies

You can set cookies by passing a variable number of Cookie objects to the cookie() method:

[sourcecode lang=”java5″]mvc.perform(get(“/color/1”)
.cookie(new Cookie(“name”, “value”)))[/sourcecode]

Custom Headers

Finally you can add specific headers to the request if none of the specialised methods provides the functionality you require.

You can set a header by passing a header name and its value.

[sourcecode lang=”java5″]mvc.perform(get(“/color/1”)
.header(“Cache-Control”, “no-cache, no-store”))[/sourcecode]If you need multiple headers you can pass multiple values

[sourcecode lang=”java5″]mvc.perform(get(“/color/1”)
.header(“Cache-Control”, “no-cache”, “no-store”))[/sourcecode]You can also pass a HttpHeaders object to the headers() method (notice the plural). This will add to any existing headers:

[sourcecode lang=”java5″]HttpHeaders headers = new HttpHeaders();
headers.setCacheControl(“no-cache, no-store”);
mvc.perform(get(“/color/1”)
.headers(headers))[/sourcecode]

Setting common customisations

If you have a lot of customisation that happens on every request, you can use a RequestPostProcessor to customise the request used.

[sourcecode lang=”java”]public interface RequestPostProcessor {

MockHttpServletRequest postProcessRequest(MockHttpServletRequest request);

}[/sourcecode]The function is called after the Builder has built the Request object. Your method should return the modified request. 

[sourcecode lang=”java”]public class MyRequestPostProcessor implements RequestPostProcessor {
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setRemoteUser(“test”);
return request;
}
}[/sourcecode]You can then use this class in your test:
[sourcecode lang=”java5″]mvc.perform(get(“/color/1”)
.with(new MyRequestPostProcessor()))[/sourcecode]You can also use a lambda if your code is small:

[sourcecode lang=”java5″]mvc.perform(get(“/color/1”)
.with(request -> {
request.setRemoteUser(“test”);
return request;
}))[/sourcecode]

In Part 2, we’ll discuss making assertions on the response.

Custom JPA functions with Hibernate

JPA comes with a number of built in functions. These are:

  • CONCAT(string1, string2): Concatenates two string fields or literals.
  • SUBSTRING(string, startIndex, length): Returns the part of the string argument starting at startIndex (1-based) and ending at length characters past startIndex.
  • TRIM([LEADING | TRAILING | BOTH] [character FROM] string: Trims the specified character from either the beginning (LEADING), the ending (TRAILING), or both (BOTH) the beginning and ending of the string argument. If no trim character is specified, the space character will be trimmed.
  • LOWER(string): Returns the lower-case of the specified string argument.
  • UPPER(string): Returns the upper-case of the specified string argument.
  • LENGTH(string): Returns the number of characters in the specified string argument.
  • LOCATE(searchString, candidateString [, startIndex]): Returns the first index of searchString in candidateString. Positions are 1-based. If the string is not found, returns 0.
  • ABS(number): Returns the absolute value of the argument.
  • SQRT(number): Returns the square root of the argument.
  • MOD(number, divisor): Returns the modulo of number and divisor.
  • CURRENT_DATE: Returns the current date.
  • CURRENT_TIME: Returns the current time.
  • CURRENT_TIMESTAMP: Returns the current timestamp.

Hibernate adds second(…), minute(…), hour(…), day(…), month(…), and year(…) for extracting various components from dates or times. Some database dialects supports a number of functions, but they’re undocumented, and are not supported across databases.

Unfortunately, if you need something more complicated, you are out of luck with the base Hibernate. Fortunately, you can add support for custom functions by creating custom dialects. This job is made easier by the fact that you can inherit from existing Dialect classes, but it does also mean that you have to provide classes for every Hibernate Dialect that you want to support.

[sourcecode lang=”java5″]package com.example.db.dialect;

import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.type.StandardBasicTypes;

public class MySQLServer2012Dialect extends SQLServer2012Dialect {
public MySQLServer2012Dialect() {
registerFunction(“DATE_ADD”, new SQLFunctionTemplate( StandardBasicTypes.DATE, “DATEADD(?3, ?2, ?1)” ));
registerFunction(“DATE_SUB”, new SQLFunctionTemplate( StandardBasicTypes.DATE, “DATEADD(?3, -?2, ?1)” ));

}
}
[/sourcecode]This registers two functions for adding and subtracting to the various components in a date. It can be used like:

[sourcecode lang=”sql”]date_sub(event.startDate, event.remindDaysBefore, day) <= current_date()[/sourcecode]This results in the following SQL:

[sourcecode lang=”sql”]DATEADD(day, -event.remindDaysBefore, event.start_date) <= getdate()[/sourcecode]Here’s the dialect for HSQLDB:

[sourcecode lang=”java5″]package com.example.db.dialect;

import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.type.StandardBasicTypes;

import java.sql.Types;

public class MyHSQLDialect extends HSQLDialect{

public MyHSQLDialect() {
registerFunction(“DATE_ADD”, new SQLFunctionTemplate( StandardBasicTypes.DATE, “( ?1 + CAST(?2 AS INTERVAL ?3))” ));
registerFunction(“DATE_SUB”, new SQLFunctionTemplate( StandardBasicTypes.DATE, “( ?1 – CAST(?2 AS INTERVAL ?3))” ));
}
}
[/sourcecode]registerFunction()takes two parameters, the function name, and a SQLFunction implementation. Fortunately, Hibernate provides a number of default implementations that make this easy to use.

The simplest implementation is NoArgSQLFunction, which is for functions that take no argument, like CURRENT_DATE()

[sourcecode lang=”java5″]registerFunction( “current_time”, new NoArgSQLFunction( “getdate”, StandardBasicTypes.TIME ) );[/sourcecode]The first argument is the SQL function name and the second is the return type from the function.

Next up is StandardSQLFunction which is for functions that are simple functions, with a name and multiple parameters:

[sourcecode lang=”java5″]registerFunction( “lower”, new StandardSQLFunction( “lower” ) );[/sourcecode]There is an optional second parameter which is the return type of the function. The one parameter constructor assumes that the return type is the same type as the first parameter.

If you require a little more complicated call, you can use SQLFunctionTemplate

[sourcecode lang=”java5″]registerFunction( “trim”, new SQLFunctionTemplate( StandardBasicTypes.STRING, “ltrim(rtrim(?1))” ) );[/sourcecode]This allows you to use a template to generate the SQL. The implementation will substitute $1, $2 etc with the parameters to the function. You can see a more complicated version at the start of the article.

The final implementation you might want to use is VarArgsSQLFunction. This allows a little more flexibility over StandardSQLFunction when you have a variable number of parameters.

[sourcecode lang=”java5″]registerFunction( “concat”, new VarArgsSQLFunction( StandardBasicTypes.STRING, “(“, “+”, “)” ) );[/sourcecode]If any of these options do not provide the flexibility that you require, you can implement the SQLFunction interface yourself. The vital function to implement is render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory), which returns the query fragment string. Here is an example from Hibernate itself that uses the native charindex() and char_length() as used by the Sybase and SQL Server dialects.

[sourcecode lang=”java5″]public class CharIndexFunction implements SQLFunction {
@Override
public boolean hasArguments() {
return true;
}

@Override
public boolean hasParenthesesIfNoArguments() {
return true;
}

@Override
public Type getReturnType(Type columnType, Mapping mapping) throws QueryException {
return StandardBasicTypes.INTEGER;
}

@Override
public String render(Type columnType, List args, SessionFactoryImplementor factory) throws QueryException {
final boolean threeArgs = args.size() > 2;
final Object pattern = args.get( 0 );
final Object string = args.get( 1 );
final Object start = threeArgs ? args.get( 2 ) : null;

final StringBuilder buf = new StringBuilder();
buf.append( “charindex(” ).append( pattern ).append( “, ” );
if (threeArgs) {
buf.append( “right(” );
}
buf.append( string );
if (threeArgs) {
buf.append( “, char_length(” ).append( string ).append( “)-(” ).append( start ).append( “-1))” );
}
buf.append( ‘)’ );
return buf.toString();
}

}[/sourcecode]