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.
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 (
updateMemberLogin
method) ofRepository
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.
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 @Transactional
annotation 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 @Transactional
annotation 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.
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.
|
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.
Business logic of Service
and @Transactional
test are carried out in domain layer.
When the test is to be performed by injecting Service
and 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 Service
as below.
Test method | Description |
---|---|
Test using dependent class | Test is performed by injecting Service and joining infrastructure layers. |
Test using mock | Test is performed by mocking all the classes on which implementation class of Service depends. |
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.
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 TicketReserveServiceImpl class. |
(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.
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
@Mock annotation
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.
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.MockMvc
etc) 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 Form
unit
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.
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.xml uses 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.
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 TransactionTokenInterceptor
and 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 (register method). |
(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 Controller
class 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 bypost
method ofMockMvcRequestBuilders
and register a mock session in the generated request bysession
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.
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 ticketSearchHelper For 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.