10.2.2. Implementation of test by layer

A list of unit test target classes for each layer, a test method, and a summary thereof are shown below.

Note that, the test method and the implementation given in this chapter is only an example, and the actual test method and implementation is to be considered in accordance with the test policy.

Layer Test method Summary
Infrastructure layer Test which only use Spring Test standard function Standard function of Spring Test is used and a test to access data is performed.
Infrastructure layer Test which use Spring Test DBUnit DBUnit and Spring Test DBUnit functions are used and a test to access data is performed.
Domain layer Test using dependent class Service is injected by using Spring Test DI function, is combined with infrastructure layer and Service test is performed.
Domain layer Test using mock Mockito is used to mock dependent classes and Service test is performed.
Application layer Test using StandaloneSetup Controller is injected by using Spring Test DI function, is combined with domain layer and infrastructure layer and Controller test is performed.
Application layer Test by using WebAppContextSetup MockMvc of Spring Test is used to apply spring-mvc.xml and applicationContext.xml created in the operation and Controller test is performed.
Application layer Test using mock Mockito is used to mock dependent classes and Controller test is performed.
Application layer Unit test of Helper Helper test is performed. For details of test method, refer test method of Service.

10.2.2.1. Unit test of infrastructure layer

This section explains about unit test of Infrastructure layer of development guidelines.

../../_images/ImplementsOfTestByLayerLayerOfTestTargetRepository.png

In infrastructure layer, the test to access data which uses MyBatis from Repository is performed. For details of how to use MyBatis3, refer Implementing Repository using MyBatis3.

Since RepositoryImpl which is auto-generated by MyBatis runs on Spring DI container,actualBean definition and SpringJUnit4ClassRunner of Spring Test provided by Spring DI function are used in the test. For details of Spring Test, refer Spring Test.

Following two methods can be listed as data verification methods after test execution. The method to be used should be determined in accordance with the business requirements given separately.

  • Fetch database status after test execution by using SELECT statement and verify the same.
  • Validate by using DBUnit and Spring Test DBUnit.

This section explains a case wherein JdbcTemplate is used as a verification method using SELECT statement. JdbcTemplate is a core class of Spring JDBC support.In JDBC API, it is necessary to acquire connection from data source, create PreparedStatement, analyze ResultSet and release the connection, however,when JdbcTemplate is used, many of these processes are concealed and data access can be performed more easily.

Note

In Application Layering, Repository interface is a deliverable of domain layer, however, it is introduced here as a unit test target of infrastructure layer. It is recommended to check on the domain layer as well that the interface with Service is correct.


10.2.2.1.1. Unit test of Repository

This section explains implementation method for unit test of Repository given below.

Test method Description
Test which only use Spring Test standard function Use JdbcTemplate to verify test results.
Test which use Spring Test DBUnit Use functions of DBUnit and Spring Test DBUnit to verify test results.

Here, explanation is given using the test for following deliverables as an example. Note that, for details of Repository implementation, refer Implementing Repository using MyBatis3.

  • Update process (updateMemberLoginmethod) of Repository interface (MemberRepository)
  • Mapping file (MemberRepository.xml)

Implementation example for test is shown below.

  • MemberRepository.java
public interface MemberRepository {

    int updateMemberLogin(Member member);
}
  • MemberRepository.xml
<mapper namespace="com.example.domain.repository.member.MemberRepository">

  <update id="updateMemberLogin" parameterType="Member">
    UPDATE member_login SET
        last_password = password,
        password = #{memberLogin.password}
    WHERE
        customer_no = #{membershipNumber}
  </update>

</mapper>

10.2.2.1.1.1. Test which only use Spring Test standard function

Files to be created for unit test of Repository which use Spring Test are shown below. Further, for setup method of database, refer Schema and test data setup (in case of a test when only Spring Test standard function is used) . Also, for setup files used while carrying out unit test by using Spring Test, refer Setting file used in the implementation example.

../../_images/ImplementsOfTestByLayerRepositorySpringTestItems.png
Names of the files to be created Explanation
MemberRepositoryTest.java Test class of MemberRepository.java.
test-context.xml Setup files supplementing setup required for carrying out unit test by using Spring Test.
setupMemberLogin.sql SQL file to set data of database used in unit test.

Note

Creation units of SQL file to be used in unit test

Here, 1 SQL is created for 1 test method. Actual creation units must be considered appropriately depending on test policy and details. Note that, if SQL file path is omitted in @Sql, SQL files are searched based on specified locations of @Sql. For details, refer Omitting SQL file path of @Sql.


How to create a test class of Repository while using Spring Test is explained.

Setup files used for testing by using data access are shown below.

  • sample-infra.xml
<import resource="classpath:/META-INF/spring/sample-env.xml" />

<!-- define the SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:/META-INF/mybatis/mybatis-config.xml" />
</bean>

<!-- scan for Mappers -->
<mybatis:scan base-package="com.example.domain.repository" />
  • sample-env.xml
  <bean id="realDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.postgresql.Driver" />
    <property name="url" value="jdbc:postgresql://localhost:5432/sample" />
    <property name="username" value="sample" />
    <property name="password" value="xxxx" />
    <property name="defaultAutoCommit" value="false" />
    <property name="maxTotal" value="96" />
    <property name="maxIdle" value="16" />
    <property name="minIdle" value="0" />
    <property name="maxWaitMillis" value="60000" />
  </bean>

  <bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
    <constructor-arg index="0" ref="realDataSource" />
  </bean>

  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>

<bean id="dateFactory" class="org.terasoluna.gfw.common.date.jodatime.DefaultJodaTimeDateFactory" />

How to create a test of Repository which use Spring Test is explained. Here, it is assumed that schema for test has already been created, MemberLogin table is set by using @Sql annotation, password of MemberLogin “ABCDE” is updated to new password “FGHIJ”, MemberLogin table is fetched after update and verified.

  • MemberRepositoryTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/sample-infra.xml",   //(1)
        "classpath:META-INF/spring/test-context.xml" }) //(1)
@Transactional // (2)
public class MemberRepositoryTest {

    @Inject
    MemberRepository target; // (3)

    @Inject
    JdbcTemplate jdbctemplate; // (4)

    @Test
    @Sql(scripts = "classpath:META-INF/sql/setupMemberLogin.sql", config = @SqlConfig(encoding = "utf-8"))
    public void testUpdateMemberLogin() {

        // (5)
        // setup test data
        MemberLogin memberLogin = new MemberLogin();
        memberLogin.setPassword("FGHIJ");
        Member member = new Member();
        member.setMembershipNumber("0000000001");
        member.setMemberLogin(memberLogin);

        // (6)
        // run the test
        int updateCounts = target.updateMemberLogin(member);

        // (7)
        MemberLogin updateMemberLogin = getMemberLogin("0000000001");

        // (8)
        // assertion
        assertThat(updateCounts, is(1));
        assertThat(updateMemberLogin.getPassword(), is("FGHIJ"));
        assertThat(updateMemberLogin.getLastPassword(), is("ABCDE"));
    }

    private Member getMemberLogin(String customerNo) {

        MemberLogin memberLogin = (MemberLogin) jdbctemplate.queryForObject(
                "SELECT * FROM member_login WHERE customer_no=?",
                new Object[] {customerNo },
                new RowMapper<MemberLogin>() {

                    public MemberLogin mapRow(ResultSet rs,
                                int rowNum) throws SQLException {

                            MemberLogin mapMemberLogin = new MemberLogin();

                            mapMemberLogin.setPassword(rs.getString(
                                    "password"));
                            mapMemberLogin.setLastPassword(rs.getString(
                                    "last_password"));
                            mapMemberLogin.setLoginDateTime(rs.getDate(
                                    "login_date_time"));
                            mapMemberLogin.setLoginFlg(rs.getBoolean(
                                    "login_flg"));

                            return mapMemberLogin;
                    }
                });

        return memberLogin;
    }
Sr. No. Description
(1)
Read sample-infra.xml and test-context.xml which retain applications required for running MemberRepository class.
(2)
When @Transactional annotation is assigned, start to end of test execution is considered as one transaction and is rolled back by default after the completion of test. If the annotation is defined at the class level, @Transactional annotation is enabled for all the test methods.
(3)
Inject MemberRepository class considered as a test target.
(4)
Inject JdbcTemplate class.
(5)
Create test data for executing a method for testing.
(6)
Execute the method for testing.
(7)
Fetch information of database after update. By using org.springframework.jdbc.core.RowMapper<T>, ResultSet fetched from database can be mapped to a specific POJO class.
(8)
Verify update count and update results.

Note

A method wherein transaction is not rolled back at the time of testing

When @Transactionalannotation is specified in a test case, it is rolled back after execution of test method by default.When you do not want to roll back for using the test data in subsequent tests, @Rollback(false) annotation or @Commit annotation is specified besides @Transactionalannotation and transaction at the time of test can be committed.

Warning

Regarding @TransactionConfiguration for Spring Framework 4.2 and subsequent versions

Since Spring Framework 4.2, you can set @Rollback or @Commit at the class level. With this, @TransactionConfiguration has been deprecated. However, in the earlier versions of Spring Framework 4.2, when the rollback is to be performed at the class level, @TransactionConfiguration(defaultRollback = true)should be set.


10.2.2.1.1.2. Test which use Spring Test DBUnit

Implementation method of unit test of Repository while using DBUnit in data access is explained. Note that, an example wherein an Excel format file (.xlsx) is used in data definition file of DBUnit is explained here. For setup method of data definition file and database, refer Test data setup (In case of a test which use Spring Test DBUnit).

Further, for using Spring Test DBUnit functions together in DBUnit, it is necessary to register com.github.springtestdbunit.TransactionDbUnitTestExecutionListener by using @TestExecutionListeners annotation. For registration method, refer Registration of TestExecutionListener.

Files to be created in unit test of Repository which use DBUnit are shown below.

../../_images/ImplementsOfTestByLayerRepositoryDbunitItems.png
Names of files to be created Description
MemberRepositoryDbunitTest.java Test class of MemberRepository.java (while linking with DBUnit)
XlsDataSetLoader.java Implementation class of DataSetLoader interface supporting Excel format. For implementation method, refer Test data setup (In case of a test which use Spring Test DBUnit).
expected_testUpdateMemberLogin.xlsx File for verification of expected results of test
setup_MemberLogin.xlsx File for test data setup
test-context.xml Setup file used while carrying out unit test by using Spring Test. Use the same setup file created by Test which only use Spring Test standard function.

Note

Creation unit of Excel file used in unit test

Here, a file for data setup and a file for verifying expected results are respectively created in 1 test method. Test policy and details should be appropriately reviewed for the actual creation unit.


How to create a test class of Repository while using DBUnit is explained.

Here, it is assumed that the schema for test is already created. By using @DatabaseSetup annotation, MemberLogin table is set, MemberLogin password is updated from “ABCDE” to new password “FGHIJ” and verified by using @ExpectedDatabase annotation.

How to create a test method of Repository by using Spring Test and DBUnit is shown below.

  • MemberRepositoryDbunitTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/sample-infra.xml",   // (1)
        "classpath:META-INF/spring/test-context.xml" }) // (1)
@TestExecutionListeners({
        DirtiesContextBeforeModesTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionDbUnitTestExecutionListener.class})
@Transactional
@DbUnitConfiguration(dataSetLoader = XlsDataSetLoader.class)
public class MemberRepositoryDbunitTest {

    @Inject
    MemberRepository target;

    @Test
    @DatabaseSetup("classpath:META-INF/dbunit/setup_MemberLogin.xlsx")
    @ExpectedDatabase( // (2)
            value = "classpath:META-INF/dbunit/expected_testUpdateMemberLogin.xlsx",
            assertionMode = DatabaseAssertionMode.NON_STRICT_UNORDERED)
    public void testUpdate() {

        // setup
        MemberLogin memberLogin = new MemberLogin();
        memberLogin.setPassword("FGHIJ");
        Member member = new Member();
        member.setMembershipNumber("0000000001");
        member.setMemberLogin(memberLogin);

        // run the test
        int updateCounts = target.updateMemberLogin(member);

        // assertion
        assertThat(updateCounts, is(1));
    }
}
Sr. No. Description
(1)
Read setup file (sample-infra.xml retained by application and test-context.xml supplementing it) required for running MemberRepository class.
(2)
After test method execution, DBUnit automatically compares and verifies the table and expected result data file, by specifying file for verifying expected test results in @ExpectedDatabase annotation.
Similar to @DatabaseSetup annotation, it can be assigned at class level and method level.
The file format is same as the test setup data file. Following values can be set in assertionMode attribute.
  • DEFAULT(or not specified): Compare all the tables and columns.
  • NON_STRICT: If a table or column that does not exist in the expected result data file exists in the actual database, it is ignored.
  • NON_STRICT_UNORDERED: Further to NON_STRICT mode, sequence of lines is also ignored.

Warning

Table with external key constraints

When database is initialized by using DBUnit for the table with external key constraint, it must be noted that sequence of dataset must be specified in order to maintain data integrity since an error occurs depending on the reference conditions.

Note

Sequence verification method

Sequence consists of a feature wherein even if a transaction is rolled back, advanced value does not return. Therefore, when a record consisting of columns numbered from a sequence is to be verified by DBUnit, one of the following measures must be employed.

  • Columns numbered from the sequence are excluded from verification
  • Execute SQL which explicitly initializes the sequence and initialize it prior to test implementation
  • Verify sequence value at the time of test execution and validate the verified value as a standard value

10.2.2.2. Unit test of domain layer

This section explains about unit test of Domain layer of development guideline.

../../_images/ImplementsOfTestByLayerLayerOfTestTargetDomain.png

Business logic of Serviceand @Transactionaltest are carried out in domain layer. When the test is to be performed by injecting Serviceand joining infrastructure layers, similar to test implementation method of Repository, test is performed by using Bean definition, and SpringJUnit4ClassRunner of Spring Test. For details of Spring Test, refer Spring Test.


10.2.2.2.1. Unit test of Service

This section explains how to implement a test of Serviceas below.

Test method Description
Test using dependent class Test is performed by injecting Serviceand joining infrastructure layers.
Test using mock Test is performed by mocking all the classes on which implementation class of Servicedepends.

Explanation is given by using the test for following deliverables as an example. Further, for details of Service implementation, refer Implementation of Service.

  • Implementation class of Service (TicketReserveServiceImpl)

An implementation example for test is shown below.

  • TicketReserveServiceImpl.java
@Service
@Transactional
public class TicketReserveServiceImpl implements TicketReserveService {

    @Inject
    ReservationRepository reservationRepository;

    @Override
    public TicketReserveDto registerReservation(Reservation reservation)
            throws BusinessException {

        List<ReserveFlight> reserveFlightList = reservation.getReserveFlightList();

        // repository access
        int reservationInsertCount = reservationRepository.insert(reservation);
        if (reservationInsertCount != 1) {
            throw new SystemException(LogMessages.E_AR_A0_L9002.getCode(),
                    LogMessages.E_AR_A0_L9002.getMessage(reservationInsertCount, 1));
        }

        String reserveNo = reservation.getReserveNo();

        Date paymentDate = reserveFlightList.get(0).getFlight().getDepartureDate();

        return new TicketReserveDto(reserveNo, paymentDate);
    }
}

Mapping file used for test is shown below.

  • ReservationRepository.xml
<mapper namespace="com.example.domain.repository.reservation.ReservationRepository">

  <insert id="insert" parameterType="Reservation">
    <selectKey keyProperty="reserveNo" resultType="String" order="BEFORE">
      SELECT TO_CHAR(NEXTVAL('sq_reservation_1'), 'FM0999999999')
    </selectKey>
    INSERT INTO reservation
    (
        reserve_no,
        reserve_date,
        total_fare,
        rep_family_name,
        rep_given_name,
        rep_age,
        rep_gender,
        rep_tel,
        rep_mail,
        rep_customer_no
    )
    VALUES
    (
        #{reserveNo},
        #{reserveDate},
        #{totalFare},
        #{repFamilyName},
        #{repGivenName},
        #{repAge},
        #{repGender.code},
        #{repTel},
        #{repMail},
        NULLIF(#{repMember.membershipNumber}, '')
    )
  </insert>

10.2.2.2.1.1. Test using dependent class

Test is performed by injecting Service and joining infrastructure layers. Files created for Service test are shown below.

../../_images/ImplementsOfTestByLayerServiceSpringTestItems.png
Names of files to be created Description
TicketReserveServiceImplTest.java Test class of TicketReserveServiceImpl.java
test-context.xml Use setup file defined in Setting file used in the implementation example.

A test creation method is explained wherein the test is performed by injecting Service implementation class for test and joining infrastructure layers.

Setup files read at the time of testing are shown below.

  • sample-domain.xml
<context:component-scan base-package="com.example.domain" />
<tx:annotation-driven />

<import resource="classpath:META-INF/spring/sample-infra.xml" />
<import resource="classpath:META-INF/spring/sample-codelist.xml" />

<bean id="resultMessagesLoggingInterceptor"
      class="org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor">
  <property name="exceptionLogger" ref="exceptionLogger" />
</bean>

<aop:config>
  <aop:advisor advice-ref="resultMessagesLoggingInterceptor"
    pointcut="@within(org.springframework.stereotype.Service)" />
</aop:config>

Test implementation example is shown below. Run TicketReserveServiceImpl#registerReservation()method for test and verify the return value. Further, for how to verify database status, refer Unit test of Repository.

  • TicketReserveServiceImplTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/sample-domain.xml", // (1)
        "classpath:META-INF/spring/test-context.xml"}) // (1)
@Transactional
public class TicketReserveServiceImplTest {

    @Inject
    TicketReserveService target;

    @Inject
    private JdbcTemplate jdbcTemplate;

    @Test
    @Sql(statements = "ALTER SEQUENCE sq_reservation_1 RESTART WITH 1") // (2)
    public void testRegisterReservation() {

        // setup
        Reservation inputReservation = new Reservation();
        inputReservation.setTotalFare(39200);
        inputReservation.setReserveNo("0000000001");
        // omitted

        // run the test
        TicketReserveDto actTicketReserveDto = target.registerReservation(
                reservation);

        // assertion
        assertThat(actTicketReserveDto.getReserveNo(), is("0000000001"));
        // omitted
    }
}
Sr. No. Description
(1)
Read setup file (sample-domain.xml retained by application and test-domain.xml supplementing it) required for running TicketReserveServiceImplclass.
(2)
SQL statement can be directly specified by using statements attribute of @Sql. Here, the sequence is initialized prior to test method execution.

Warning

Transaction management at the time of testing

When @Transactional annotation is assigned to a test case, steps from start of test execution till end becomes one transaction. Hence, when Service class assigned with @Transactional annotation is called from test case, it must be noted that the transaction is inherited from the test case. For example, when the transaction propagation method is default, (REQUIRED),test processes are executed in the transactions started in test case and the timing of the commit and rollback is also considered at the end of the test. For transaction propagation methods, refer Information required for “Declarative transaction management”.

10.2.2.2.1.2. Test using mock

Files to be created in unit test of Service which is done by mocking all the dependent classes of Service are shown below.

../../_images/ImplementsOfTestByLayerServiceMockItems.png
Names of files to be created Description
TicketReserveServiceImplMockTest.java Test class of TicketReserveServiceImpl.java(while using mock)

How to create a test method while mocking the class that depends on implementation class of Service for test. Here, mocking is done for ReservationRepository#insert() method, the method for which the mocking is carried out by TicketReserveServiceImpl#registerReservation() method is called and return value for testing is verified.

  • TicketReserveServiceImplMockTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

public class TicketReserveServiceImplMockTest {

    @Rule // (1)
    public MockitoRule mockito = MockitoJUnit.rule();

    @Mock // (2)
    ReservationRepository reservationRepository;

    @InjectMocks // (3)
    private TicketReserveServiceImpl target;

    @Test
    public void testRegisterReservation() {

        // setup
        Reservation inputReservation = new Reservation();
        inputReservation.setTotalFare(39200);
        inputReservation.setReserveNo("0000000001");
        // omitted

        when(reservationRepository.insert(inputReservation)).thenReturn(1); // (4)

        // run the test
        TicketReserveDto ticketReserveDto = target.registerReservation(inputReservation);

        // assertion
        verify(reservationRepository).insert(inputReservation); // (5)
        assertThat(ticketReserveDto.getReserveNo(), is("0000000001"));
        // omitted
    }
}
Sr. No. Description
(1)
Declaration for mock initialization and injection on the basis of annotation. For details, refer Generating a mock.
(2)
MemberRepository which depend on TicketReserveServiceImpl is mocked by assigning @Mockannotation For details, refer Generating a mock.
(3)
Mock objects are automatically assigned by assiging @InjectMocks annotation. For details, refer Generating a mock.
(4)
When the argument is inputReservation for insert method of ReservationRepository, set so as to return “1” as return value. For mocking of method, refer Mocking of method.
(5)
Verify that insert method of ReservationRepository has been called once by passing inputReservation in the argument. For verification of method which is mocked, refer Validation of mocked method.

10.2.2.3. Unit test of application layer

10.2.2.3.1. Unit test target for application layer

This section explains about unit test of Application layer of development guideline.

../../_images/ImplementsOfTestByLayerLayerOfTestTargetApplication.png

A test for verifying logic of Controller and Helper is carried out in the application layer. Following items are verified for the Controller.

  • @RequestMapping (request path, HTTP method and request parameter)
  • VIEW name to be returned

View is originally included in the application layer, however it is excluded from this guideline.

Spring Test provides a support class (org.springframework.test.web.servlet.MockMvcetc) for testing controller class. Controller uses SpringJUnit4ClassRunner of Spring Test which provides MockMVC, in order to perform test by sending a pseudo request by using MockMVC. MockMvc consists of a mechanism to send a pseudo request to the Controller and a test can be performed simulating deployed application. For details of MockMVC, refer MockMvc.

Note

Validation test of Form

Form test should be carried out in a manner similar to actual operation by combining with Controller. If all the patterns of Validation are combined with Controller, it will increase load on testing. Hence, for a simple Validation confirmation, Validation can be verified by Formunit by separating it from Controller. For test method, Unit test of Validator implemented by Bean Validation can be implemented by using test target.


10.2.2.3.2. Unit test of Controller

Implementation method for unit test of Controller is explained below.

Test method Description
Test using StandaloneSetup Test is performed by using default context provided by Spring Test and reading specified setup file.
Test by using WebAppContextSetup Test is performed using applicationContext.xml and spring-mvc.xml which are actually used.
Test using mock Test is performed by mocking all the classes on which Controller is dependent.

A test example for following deliverables is explained. For details of Controller implementation, refer Implementing Controller.

  • Controller class (TicketSearchController)
  • Controller class (MemberRegisterController)

Note that, when you want to carry out test by combining injection and mocking, implement the same by combining following implementation methods as appropriate.

10.2.2.3.2.1. Test using StandaloneSetup

For the unit test of Controller when the dependency class of Controller can be used without the necessity of mocking, files to be created by StandaloneSetup are as below.

../../_images/ImplementsOfTestByLayerControllerStandaloneSetupItems.png
Names of files to be created Description
MemberRegisterControllerStandaloneTest.java Test class of MemberRegisterController.java
spring-mvc-test.xml Setup file which extracts component-scan for test, in order to read application layer dependent components.
test-context.xml Setup file to be used while testing Controller with domain layer and infrastructure layer.

It is preferable to perform test by using spring-mvc.xml, however context created by Spring Test and context created by Spring MVC may conflict and test execution might not be possible. As a countermeasure, only the settings necessary for testing are extracted and a setting file for test is provided.

Setup files for which only necessary settings are extracted are shown below.

  • spring-mvc-test.xml
<context:component-scan base-package="com.example.app" />

A method to create a test is explained when a class that is dependent on the Controller class for testing such as ServiceImpl is to be injected. Note that, for verification method while accessing data in the test, refer Unit test of Repositoryand for the method of verifying logic of domain layer to be called, refer Unit test of Service.

Implementation example of Controller for test is shown below.

  • MemberRegisterController.java
@Controller
@RequestMapping("member/register")
@TransactionTokenCheck("member/register")
public class MemberRegisterController {

    @TransactionTokenCheck(type = TransactionTokenType.IN)
    @RequestMapping(method = RequestMethod.POST)
    public String register(@Validated MemberRegisterForm memberRegisterForm,
        BindingResult result, Model model, RedirectAttributes redirectAttributes) {

        if (result.hasErrors()) {
            throw new BadRequestException(result);
        }

        // omitted

        return "redirect:/member/register?complete";
    }
}

Here, register method of MemberRegisterController class for testing is called, and it is confirmed that request mapping and returned view is redirected (testRegisterConfirm01) and BadRequestException is thrown when an invalid value is sent (testRegisterConfirm02).

A test method is explained below wherein a class for testing that depends on the Controller class such as ServiceImpl class is injected. Further, for verification method while accessing data in the test, refer Unit test of Repository.

  • MemberRegisterControllerStandaloneTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:META-INF/spring/applicationContext.xml", // (1)
        "classpath:META-INF/spring/test-context.xml",       // (1)
        "classpath:META-INF/spring/spring-mvc-test.xml"})   // (1)
public class MemberRegisterControllerStandaloneTest {

    @Inject
    MemberRegisterController target;

    MockMvc mockMvc;

    @Before
    public void setUp() {

        // setup
        mockMvc = MockMvcBuilders.standaloneSetup(target).alwaysDo(log()).build(); // (2)
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        // setup and run the test
        mockMvc.perform(post("/member/register")
                    // omitted
                    .param("password", "testpassword")          // (3)
                    .param("reEnterPassword", "testpassword"))) // (3)
                    // assert
                    .andExpect(status().is(302))                                  // (4)
                    .andExpect(view().name("redirect:/member/register?complete")) // (4)
                    .andExpect(model().hasNoErrors());                            // (4)
    }

    @Test
    public void testRegisterConfirm02() throws Exception {

        try {
            // setup and run the test
            mockMvc.perform(post("/member/register")
                    // omitted
                    .param("password", "testpassword")
                    .param("reEnterPassword", "")) // (5)
                    // assert
                    .andExpect(status().is(400))
                    .andExpect(view().name("common/error/badRequest-error"))
                    .andReturn();

            fail("test failure!");
        } catch (Exception e) {

            // assert
            assertThat(e, is(instanceOf(NestedServletException.class)));         // (6)
            assertThat(e.getCause(), is(instanceOf(BadRequestException.class))); // (6)
        }
    }
}
Sr. No. Description
(1)
Read the setup files (applicationContext.xml retained by the application and test-context.xml, spring-mvc-test.xml supplementing the same) required for running Service dependent on MemberRegisterController and Repository. test-context.xmluses Setting file used in the implementation example.
(2)
Use Controller generated from loaded Bean definition and setup MockMvc. For setup details, refer MockMvc setup.
(3)
Send a request by POST method for member/register, in order to call registerConfirm method of MemberRegisterController class. Set information of Form in request parameter. For setup method of request data, refer Setting request data and for implementation method for sending a request, refer Implementation of sending a request.
(4)
Use MvcResult fetched by andExpect method of ResultActions returned from perform method and verify validity of execution results. For details of verification method, refer Implementation of execution results verification.
(5)
Send invalid input value.
(6)
Since SystemExceptionResolver is not enabled, NestedServletException is notified to servlet container without exception handling. Verify that exception expected in Controller is thrown from the exception obtained by getCause method of NestedServletException.

10.2.2.3.2.2. Test by using WebAppContextSetup

For unit test of Controller when the dependency class of Controller can be used and mocking is not required, files to be created in WebAppContextSetup are shown below.

../../_images/ImplementsOfTestByLayerControllerWebAppContextSetupItems.png
Names of files to be created Description
MemberRegisterControllerWebAppContextTest.java Test class of MemberRegisterController.java

It is possible to check the request to the path and View name returned by the Controller in the example of Test using StandaloneSetup,however,since functions which are used by adding to Spring such as TransactionTokenInterceptorand SystemExceptionResolver are not applied, it is not possible to determine whether transaction token check is correctly configured or the transition to error page is correct. In such a case, Interceptor and ExceptionResolver which are used by adding to Spring can be applied automatically at the time of test, by setting up MockMvc using webAppContextSetup.

Differences after comparing the test explained in Test using StandaloneSetupand test when @TransactionTokenCheck annotation and SystemExceptionResolver are enabled are explained here.

Implementation example of Controller for testing is shown below.

  • MemberRegisterController.java
@Controller
@RequestMapping("member/register")
@TransactionTokenCheck("member/register")
public class MemberRegisterController {

    @TransactionTokenCheck(type = TransactionTokenType.BEGIN) // (1)
    @RequestMapping(method = RequestMethod.POST, params = "confirm")
    public String registerConfirm(@Validated MemberRegisterForm memberRegisterForm,
        BindingResult result, Model model) {

        // omitted

        return "C1/memberRegisterConfirm";
    }

    @TransactionTokenCheck(type = TransactionTokenType.IN) // (1)
    @RequestMapping(method = RequestMethod.POST)
    public String register(@Validated MemberRegisterForm memberRegisterForm,
        BindingResult result, Model model, RedirectAttributes redirectAttributes) {

        if (result.hasErrors()) {
            throw new BadRequestException(result); // (2)
        }

        // omitted

        return "redirect:/member/register?complete";
    }
}
Sr. No. Description
(1)
Invalidate invalid requests by setting @TransactionTokenCheck annotation. For transaction token check, refer Transaction Token Check.
(2)
If a validation error occurs at the time of request, it is considered as tampering and an error is thrown.

At first, differences in test creation methods when @TransactionTokenCheck is enabled, are shown below. Further, for verification methods while accessing data in the test, refer Unit test of Repository.

  • MemberRegisterControllerWebAppContextTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({@ContextConfiguration(                                   // (1)
        "classpath:META-INF/spring/applicationContext.xml"),                // (1)
        @ContextConfiguration("classpath:META-INF/spring/spring-mvc.xml")}) // (1)
@WebAppConfiguration                                                        // (1)
public class MemberRegisterControllerWebAppContextTest {

    @Inject
    WebApplicationContext webApplicationContext; // (2)

    MockMvc mockMvc;

    @Before
    public void setUp() {

        // setup
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) // (2)
                .alwaysDo(log()).build();
    }

    @Test
    public void testRegisterConfirm01() throws Exception {

        // setup and run the test
        MvcResult mvcResult = mockMvc.perform(post("/member/register") // (3)
                    .param("confirm", "")                              // (3)
                    // omitted
                    .param("password", "testpassword")                 // (3)
                    .param("reEnterPassword", "testpassword"))         // (3)
                    // assert
                    .andExpect(status().is(200))
                    .andExpect(view().name("C1/memberRegisterConfirm"))
                    .andReturn();

        TransactionToken actTransactionToken = (TransactionToken) mvcResult.getRequest()
                .getAttribute(TransactionTokenInterceptor.NEXT_TOKEN_REQUEST_ATTRIBUTE_NAME); // (4)

        MockHttpSession mockSession = (MockHttpSession) mvcResult.getRequest().getSession();  // (5)

        // setup and run the test
        mockMvc.perform(post("/member/register")              // (6)
                    // omitted
                    .param("password", "testpassword")        // (6)
                    .param("reEnterPassword", "testpassword") // (6)
                    .param(TransactionTokenInterceptor.TOKEN_REQUEST_PARAMETER,
                            actTransactionToken.getTokenString()) // (6)
                    .session(mockSession)) // (6)
                    // assert
                    .andExpect(status().is(302))                                   // (7)
                    .andExpect(view().name("redirect:/member/register?complete")); // (7)
    }
}
Sr. No. Description
(1)
Read spring-mvc.xml to run Interceptor and ExceptionResolver customized for operation.
(2)
Use Web application context generated from loaded Bean definition and set up MockMvc.
(3)
Send a request for the method set by @TransactionTokenCheck(type = TransactionTokenType.BEGIN) in order to generate a method transaction token.
(4)
Fetch transaction token from request attribute to inherit the transaction token from BEGIN request (registerConfirm method) to IN request (registermethod).
(5)
It is necessary to refer to same session in the next request as well for retaining the transaction token issued by the server side, in the session, however, It is specified in MockMvc that the same session must be used explicitly as new sessions are used for each request.
(6)
Send the request again to request path (member/register) with the POST method. Set information of Form and transaction token fetched in (4), in request parameter, and set the session fetched in (5) in the session.
(7)
Verify that the transaction token check error has not occurred to confirm that the settings of transaction token check are correct.

Differences in test creation methods when SystemExceptionResolver is enabled are explained next.

Definition example of SystemExceptionResolver is shown below.

  • spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
  <property name="order" value="3" />
  <property name="exceptionMappings">
    <map>
      <entry key="InvalidTransactionTokenException" value="common/error/token-error" />
      <entry key="BadRequestException" value="common/error/badRequest-error" />
      <entry key="Exception" value="common/error/system-error" />
    </map>
  </property>
  <property name="statusCodes">
    <map>
      <entry key="common/error/token-error" value="409" />
      <entry key="common/error/badRequest-error" value="400" />
    </map>
  </property>
  <property name="excludedExceptions">
      <array>
          <value>org.springframework.web.util.NestedServletException</value>
      </array>
  </property>
  <property name="defaultStatusCode" value="500" />
  <property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
  <property name="preventResponseCaching" value="true" />
</bean>

Differences in test creation method are explained below.

  • MemberRegisterControllerWebAppContextTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({@ContextConfiguration(
        "classpath:META-INF/spring/applicationContext.xml"),
        @ContextConfiguration("classpath:META-INF/spring/spring-mvc.xml")})
@WebAppConfiguration
public class MemberRegisterControllerWebAppContextTest {

    @Inject
    WebApplicationContext webApplicationContext;

    MockMvc mockMvc;

    @Before
    public void setUp() {

        // setup
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .alwaysDo(log()).build();
    }

    @Test
    public void testRegisterConfirm02() throws Exception {

        // omitted

        // setup and run the test
        mvcResult = mockMvc.perform(post("/member/register")
                    .param("password", "testpassword")
                    .param("reEnterPassword", "") // (1)
                    .param(TransactionTokenInterceptor.TOKEN_REQUEST_PARAMETER,
                            actTransactionToken.getTokenString()) // (2)
                    .session(mockSession)) // (2)
                    // assert
                    .andExpect(status().is(400))                             // (3)
                    .andExpect(view().name("common/error/badRequest-error")) // (3)
                    .andReturn();

        // assert
        Exception exception = mvcResult.getResolvedException();                   // (4)
        assertThat(exception, is(instanceOf(BadRequestException.class)));         // (4)
        assertThat(exception.getMessage(), is("不正リクエスト(パラメータ改竄)")); // (4)
    }
}
Sr. No. Description
(1)
Error is thrown in register method by making information of Form invalid value.
(2)
As described earlier, set information of generated transaction token.
(3)
Verify that status code of defined error, transition destination of error page are correctly set since SystemExceptionResolver is enabled here.
(4)
Verify that the expected error is thrown from the error that handle exceptions in SystemExceptionResolver.

Note

When Session is used

When the Controllerclass uses a Session, test is performed by using org.springframework.mock.web.MockHttpSession.

  • Example of test method which use MockHttpSession
public class SessionControllerTest {

    // (1)
    MockHttpSession mockSession = new MockHttpSession();

    // omitted

    @Test
    public void testSession() throws Exception {
        String formName = "todoForm";

        TodoForm form = new TodoForm();
        String todoId = "1111";
        String todoTitle = "test";

        form.setTodoId(todoId);
        form.setTodoTitle(todoTitle);

        // (2)
        mockSession.setAttribute(formName, form);

        // (3)
        ResultActions results = mockMvc.perform(post("/todo/operation")
            .param("create", "create")
            .param("todoId", todoId)
            .param("todoTitle", todoTitle)
            .session(mockSession));

        // (4)
        results.andExpect(request().sessionAttribute(formName, isA(TodoForm.class)));

        // omitted

        // (5)
        results = mockMvc.perform(get("/todo/create").param("redo", "redo"));
        results.andExpect(request().sessionAttribute(formName, isA(TodoForm.class)));

        // omitted
    }
}
Sr. No. Description
(1)
Generate a mock object of session. For class details, refer MockHttpSession Javadoc.
(2)
Set the object to be stored in the mock object of the generated session.
(3)
Generate a mock request by post method of MockMvcRequestBuilders and register a mock session in the generated request by session method.
(4)
Verify that object set in (2) is stored in the session scope.
(5)
Issue a request again and check whether the object stored in the session scope is retained.

10.2.2.3.2.3. Test using mock

Files to be created for unit test of Controller when it is necessary to mock dependency class of Controller are as below.

../../_images/ImplementsOfTestByLayerControllerMockTest.png
Names of files to be created Description
TicketSearchControllerMockTest.java Test class of TicketSearchController.java

A test creation method when mocking is done for class which is dependent on Controller class for testing is explained.

Implementation example of Controller for test is shown below.

  • TicketSearchController.java
@Controller
@RequestMapping("ticket/search")
public class TicketSearchController {

    @Inject
    TicketSearchHelper ticketSearchHelper;

    @RequestMapping(method = RequestMethod.GET, params = "form")
    public String searchForm(Model model) {

        model.addAttribute(ticketSearchHelper.createDefaultTicketSearchForm());

        model.addAttribute(ticketSearchHelper.createFlightSearchOutputDto());

        model.addAttribute("isInitialSearchUnnecessary", true);

        return "B1/flightSearch";
    }
}

Implementation example for Controller test is shown below.

  • TicketSearchControllerMockTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

public class TicketSearchControllerMockTest {

    @Rule // (1)
    public MockitoRule mockito = MockitoJUnit.rule();

    @InjectMocks // (2)
    TicketSearchController target;

    @Mock // (3)
    TicketSearchHelper ticketSearchHelper;

    MockMvc mockMvc;

    @Before
    public void setUp() {

        // setup
        TicketSearchForm ticketSearchForm = new TicketSearchForm();
        ticketSearchForm.setFlightType(FlightType.RT);
        ticketSearchForm.setDepAirportCd("HND");
        // omitted

        when(ticketSearchHelper.createDefaultTicketSearchForm()).thenReturn(ticketSearchForm); // (4)

        mockMvc = MockMvcBuilders.standaloneSetup(target).alwaysDo(log()).build();
    }

    @Test
    public void testSearchForm() throws Exception {

        // setup and run the test
        MvcResult mvcResult = mockMvc.perform(get("/ticket/search").param("form", ""))
                    // assert
                    .andExpect(status().is(200))
                    .andExpect(view().name("B1/flightSearch"))
                    .andReturn();

        // assert
        verify(ticketSearchHelper).createDefaultTicketSearchForm(); // (5)

        // omitted
    }
}
Sr. No. Description
(1)
Declaration for mock initialization and injection on the basis of annotation. For details, refer Generating a mock.
(2)
By assigning @Mock annotation, mocking is done for TicketSearchHelper which is dependent on TicketSearchController. For details, Generating a mock.
(3)
By assigning @InjectMocks annotation, mock objects are automatically assigned. For details, refer Generating a mock.
(4)
In all test methods, set return value of createMockForm method as a return value of createDefaultTicketSearchForm method of ticketSearchHelperFor mocking of method, refer Mocking of method.
(5)
Verify that createDefaultTicketSearchForm method of ticketSearchHelper is called once. For verification of method which has undergone mocking, referValidation of mocked method.

10.2.2.3.3. Unit test of Helper

Unit test of Helper can be performed by testing with the implementation same as Service. For implementation method, refer Unit test of Service.