Integration Test with Spring Boot

Tinytella(トリン)
Goalist Blog
Published in
10 min readJul 30, 2023

Testing is always an indispensable but also the most painful part of the software development process. It took me several hours to research in order to have a basic understanding to write a test.

Therefore, in this article, based on my experience, I would like to summarize how to write a basic integration test for Restful APIs in Spring Boot application.

I. What is Integration Test?

  • As mentioned in the name, this type of testing mainly focuses on testing how the application works after integrating many layers together.
  • While unit tests are used to test each application’s component individually and independently, integration tests allow us to make sure the entire process is working as well as it should be.
  • Unlike unit tests, integration tests will use actual data and work exactly as its behavior in production mode. For that reason, we should distinguish the database used for integration tests.

Technologies that are used in this article’s example:

  • Java 17
  • Maven (3.8.1)
  • Spring Boot (3.0.6)
  • Spring Security (6.0.3)
  • JUnit 5
  • Liquibase (4.21.1)
  • MySQL (8.0.33)

II. How to implement Integration Testing

2.1. Maven Dependencies

We’ll need to add some necessary dependencies:

<dependencies>
<!-- Base dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<!-- For testings -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.2. Configure MySQL Database

As I mentioned above, we should prepare separate database for testing.

We’ll add one more configuration file named: application-test.yaml

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/database_test?characterEncoding=utf8&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: root
liquibase:
change-log: classpath:db/changelog/master-log.xml

* We’ll have another article to explain how to use Liquibase in details.

2.3. Add JPA entities

We assume that there will be three main entities: Accounts, Applicants and Job Posts.

a. Account entity

package jp.co.example.integration.test.model;

import jakarta.persistence.*;
import jp.co.goalist.bext.enums.AccountType;
import lombok.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Cacheable
@SQLDelete(sql = "UPDATE accounts SET is_deleted = true WHERE id=?")
@Where(clause = "is_deleted=false")
@Entity(name = "accounts")
@Cache(region = "accountCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Account extends BaseEntity {

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

@Column
private String fullName;

@Column
private String email;

@Column
private String password;

@Column
private String accountType;

@Column
private Long associatedId;

@Column
private LocalDateTime lastLoginDate;

public AccountType getAccountType() {
return this.accountType == null ? null : AccountType.fromValue(this.accountType);
}

public void setAccountType(AccountType accountType) {
this.accountType = accountType == null ? null : accountType.getId();
}
}

b. Job Posts

package jp.co.example.integration.test.model;
import jakarta.persistence.*;
import jp.co.goalist.bext.enums.EmploymentStatus;
import jp.co.goalist.bext.enums.ManuscriptStatus;
import jp.co.goalist.bext.enums.SalaryType;
import lombok.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Cacheable
@SQLDelete(sql = "UPDATE job_posts SET is_deleted = true WHERE id=?")
@Where(clause = "is_deleted=false")
@Entity(name = "job_posts")
@Cache(region = "jobPostCache", usage = CacheConcurrencyStrategy.READ_WRITE)
public class JobPost extends BaseEntity {

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

private String title;

private String publicStatus;

private String employmentStatus;

@Column(columnDefinition = "TEXT")
private String introduction;

@Column(columnDefinition = "TEXT")
private String summary;

private String salaryType;

private Integer minSalary;

private Integer maxSalary;

@Column(columnDefinition = "TEXT", name = "salary_matters")
private String salaryMatter;

private String totalClick;

@OneToMany(mappedBy = "post", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
@ToString.Exclude
private List<Image> images = new ArrayList<>();

public ManuscriptStatus getPublicStatus() {
return this.publicStatus == null ? null : ManuscriptStatus.fromValue(this.publicStatus);
}

public void setPublicStatus(ManuscriptStatus publicStatus) {
this.publicStatus = publicStatus == null ? null : publicStatus.getId();
}

public SalaryType getSalaryType() {
return this.salaryType == null ? null : SalaryType.fromValue(this.salaryType);
}

public void setSalaryType(SalaryType salaryType) {
this.salaryType = salaryType == null ? null : salaryType.getId();
}

public void setEmploymentStatus(EmploymentStatus employmentStatus) {
this.employmentStatus = employmentStatus == null ? null : employmentStatus.getId();
}

public EmploymentStatus getEmploymentStatus() {
return this.employmentStatus == null ? null : EmploymentStatus.fromValue(this.employmentStatus);
}
}

c. Applicants

package jp.co.example.integration.test.model;

import jakarta.persistence.*;
import jp.co.goalist.bext.enums.EmploymentStatus;
import jp.co.goalist.bext.enums.FinalEducation;
import jp.co.goalist.bext.enums.Gender;
import jp.co.goalist.bext.util.StringListConverter;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Cacheable
@org.hibernate.annotations.Cache(region = "applicantCache", usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity(name = "applicants")
public class Applicant extends BaseEntity {

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

@Column
private String fullName;

@Column
private String hiraganaName;

@Column
private String email;

@Column
private String tel;

@Column
private String gender;

@Column
private Date birthday;

@Column
private String prefecture;

@Column
private String city;

@Column
private String employmentStatus;

public void setEmploymentStatus(EmploymentStatus employmentStatus) {
this.employmentStatus = employmentStatus == null ? null : employmentStatus.getId();
}

public EmploymentStatus getEmploymentStatus() {
return this.employmentStatus == null ? null : EmploymentStatus.fromValue(this.employmentStatus);
}

public void setGender(Gender gender) {
this.gender = gender == null ? null : gender.getId();
}

public Gender getGender() {
return this.gender == null ? null : Gender.fromValue(this.gender);
}
}

2.4. Add Controllers

We’ll use OpenApi to generate the controller layers for Applicants and Job Posts.

a. ApplicantsApi.java

@RequestMapping("/media")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-07-20T10:18:56.531406500+07:00[Asia/Saigon]")
@Validated
@Tag(name = "Applicant Pages", description = "Applicant screen")
public interface ApplicantsApi {
/**
* GET /applicants/profile : Get applicant's profile
*
* @return OK (status code 200)
* or Bad Validation (status code 400)
* or Unauthorized (status code 401)
* or Forbidden (status code 403)
* or Internal Server Error (status code 500)
*/
@Operation(
operationId = "getApplicantProfile",
summary = "Get applicant's profile",
tags = { "Applicant Pages" },
responses = {
@ApiResponse(responseCode = "200", description = "OK", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = DataResponse.class))
}),
@ApiResponse(responseCode = "400", description = "Bad Validation", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))
}),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))
}),
@ApiResponse(responseCode = "403", description = "Forbidden", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))
}),
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))
})
}, security = {
@SecurityRequirement(name = "Bearer")
}
)
@RequestMapping(
method = RequestMethod.GET,
value = "/applicants/profile",
produces = { "application/json" }
)
@PreAuthorize("hasAuthority('APPLICANT')")
ResponseEntity<DataResponse> _getApplicantProfile();
}

b. ApplicantsApiController.java

@Controller
@RequiredArgsConstructor
public class ApplicantsApiController implements ApplicantsApi {

private static final Logger logger = LoggerFactory.getLogger(ApplicantsApiController.class);
private final ApplicantService applicantService;

@Override
public ResponseEntity<DataResponse> _getApplicantProfile() {
logger.info("_getApplicantProfile");
return applicantService._getApplicantProfile();
}
}

c. JobPostsApi.java

@RequestMapping("/media")
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-07-20T10:18:56.531406500+07:00[Asia/Saigon]")
@Validated
@Tag(name = "Job Posts Pages", description = "Job Posts screen")
public interface JobPostsApi {
/**
* GET /jobPosts/{postId} : Display details of a job post
*
* @param postId 記事ID (required)
* @param isClickCount クリック数を増やすかどうか (optional, default to false)
* @return OK (status code 200)
* or Internal Server Error (status code 500)
*/
@Operation(
operationId = "getJobPostDetails",
summary = "Display details of a job post",
tags = { "Job Posts Pages" },
responses = {
@ApiResponse(responseCode = "200", description = "OK", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = DataResponse.class))
}),
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))
})
}
)
@RequestMapping(
method = RequestMethod.GET,
value = "/jobPosts/{postId}",
produces = { "application/json" }
)
ResponseEntity<DataResponse> _getJobPostDetails(
@Parameter(name = "postId", description = "記事ID", required = true, in = ParameterIn.PATH) @PathVariable("postId") Long postId,
@Parameter(name = "isClickCount", description = "クリック数を増やすかどうか ", in = ParameterIn.QUERY) @Valid @RequestParam(value = "isClickCount", required = false, defaultValue = "false") Boolean isClickCount
);
}

d. JobPostApiController.java

@Controller
@RequiredArgsConstructor
public class JobPostsApiController implements JobPostsApi {

private static final Logger logger = LoggerFactory.getLogger(JobPostsApiController.class);
private final JobPostService postService;

@Override
public ResponseEntity<DataResponse> _getJobPostDetails(Long postId, Boolean isClickCount) {
logger.info("_getJobPostDetails, postId {} ", postId);
return postService.getJobPostDetails(postId, isClickCount);
}
}

2.5. Add Services

a. Accounts Service

@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;

public Account getCurrentLoggedInUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Objects.isNull(authentication)) {
throw new UnauthorizedException(ErrorMessage.UNAUTHORIZED_ERROR.getDescription());
}
String loggedInEmail = authentication.getName();
return accountRepository.findEnabledAccountByEmail(loggedInEmail)
.orElseThrow(() -> new UnauthorizedException(
String.format(ErrorMessage.NOT_FOUND_ERROR.getDescription(),
"現在のアカウント",
loggedInEmail)));
}
}

b. Applicants Service

@Service
@RequiredArgsConstructor
public class ApplicantService {

private final AccountService accountService;

public ResponseEntity<DataResponse> _getApplicantProfile() {
Account account = accountService.getCurrentLoggedInUser();
Applicant applicant = getApplicantWithRelationsByAssociatedId(account.getAssociatedId());
ApplicantProfileDto applicantProfileDto = APPLICANT_MAPPER.toProfileDto(applicant);
return ResponseUtil.createDataResponse(applicantProfileDto, EntityName.APPLICANT);
}
}

c. Job Posts Service

@RequiredArgsConstructor
@Service
public class JobPostService {
private final JobPostRepository postRepository;

protected JobPost getExistingPostById(Long postId) {
Optional<JobPost> opPost= postRepository.findById(postId);
if (opPost.isEmpty()) {
throw new NotFoundException(ErrorResponse.builder()
.statusCode(ErrorMessage.NOT_FOUND_ERROR.getCode())
.message(String.format(
ErrorMessage.NOT_FOUND_ERROR.getDescription(),
EntityName.POST,
postId)).build());
}
return opPost.get();
}

public ResponseEntity<DataResponse> getJobPostDetails(Long postId) {
JobPost jobPost = getExistingPostById(postId);
JobPostDetailsDto jobPostDetails = JOB_POST_MAPPER.toJobPostDetailsDto(jobPost);
jobPostDetails.setImages(getImages(jobPost.getImages()));
return ResponseUtil.createDataResponse(jobPostDetails, EntityName.POST);
}
}

*We won’t go any deeper into the Repository layer in this article, so the code sample will stop at Service layer.

2.6. Add Integration tests

Before providing you code samples, I’m gonna note a few important things here.

***Important Notes:

  1. By convention, I often name my test classes as follows:

Unit tests: **SuccessUnitTest.java, **FailUnitTest.java

Integration tests: **SuccessIntegrationTest.java, **FailIntegrationTest.java

2. Using @Nested to group all cases related to one API, the group name being the API method name.

The reason is that when you are working on a large project, searching by API method name is really helpful.

3. Using @Sql to prepare test data. There are two levels that would be useful for integration tests.

  • Class Level: When @Sql is placed on the class name, it will run as @BeforeAll, which means it executes once before all the test methods are executed. Depending on the specification of each project , we could use it to create default accounts for private APIs.
  • Method Level: When @Sql is placed on method name, it will run as @BeforeEach, which means it executes once before the test method runs.

* Remember to turn on Merge mode to run both levels; otherwise, method level will override the configuration on class level.

@SqlMergeMode(MERGE)

4. Using @ActiveProfiles to specify which configuration file to use when running tests

@ActiveProfiles("test")

5. Using @Transactional annotation to clean the data after each test execution. If you would like to have more controls on deleting data, you could use @AfterEach annotation instead.

Be careful because LazyInitializationException error might happen when not using @Transactional.

6. Use @WithUserDetails to simulate the security layer. In case you don’t want to handle login process and add access token manually to every private API call, you can come up with using this annotation.

_____________________________________________________

In this article, I have also prepared a simple test data as follows.

This file includes basic accounts and master data in the system used for all test cases.

// initial_data_1.0_setup.sql
INSERT INTO `agencies`(id, `agency_name`,`created_date`,`updated_date`,`created_by`,`updated_by`)
VALUES (1, "あいうえお株式会社", current_timestamp(), current_timestamp(), "System" , "System" );
INSERT INTO `agency_departments`(`id`, `agency_id`,`department_name`,`created_date`,`updated_date`,`created_by`,`updated_by`)
VALUES (1, 1,"東京営業1", current_timestamp(), current_timestamp(), "System" , "System");

INSERT INTO `accounts`
(`id`, `email`, `password`, `full_name`, `account_type`, `associated_id`, `department_id`, `is_temp_password`, `created_date`, `updated_date`, `created_by`, `updated_by`)
VALUES
(1, "project_manager@gmail.com", "$2a$10$lxSv6HOJL66LpCpcszDsj.hpZSBPUW2rNHNJqURzpplpJTAEMflka", "クライアント", "MANAGER", 1, 1, b'0', current_timestamp(), current_timestamp(), 'System' , 'System');

INSERT INTO `applicants`
(`id`, `full_name`, `hiragana_name`, `email`, `tel`, `gender`, `birthday`, `prefecture`, `city`,
`current_employment_status`, `created_date`, `updated_date`, `created_by`, `updated_by`)
VALUES
(1, "青森 神社", "ヤマモリ ジンジャ", "applicant_001@gmail.com", "080-3945-6778", "FEMALE", "1995-08-13", "東京都", "渋谷区",
"CONTRACT", current_timestamp(), current_timestamp(), 'System', 'System');

INSERT INTO `accounts`
(`id`, `email`, `password`, `full_name`, `account_type`, `associated_id`, `department_id`, `is_temp_password`, `created_date`, `updated_date`, `created_by`, `updated_by`)
VALUES
(2, "applicant_001@gmail.com", "$2a$10$lxSv6HOJL66LpCpcszDsj.hpZSBPUW2rNHNJqURzpplpJTAEMflka", "クライアント", "APPLICANT", 1, NULL, b'0', current_timestamp(), current_timestamp(), 'System' , 'System');

_____________________________________________________

For now, let’s create two test cases for success scenarios.

  1. Public Restful API

In the given example above, we have one public URL which is /media/jobPosts/{postId}

Since it’s public, anyone can make a call to this API.

a. Create new test class: JobPostControllerSuccessIntegrationTest.java

b. Prepare test data

This file includes special logical data for each API execution.

// jobPosts_1.0_setup.sql
INSERT INTO `companies`(`id`, `company_code`, `catch_copy`, `summary`, `company_name`, `representative_name`, `created_date`, `updated_date`, `created_by`, `updated_by`)
VALUES (1, "1504", "アットホームな会社です", "アットホームな会社です", "あいうえお会社", "田中一/中田仁", current_timestamp(), current_timestamp(), 'System', 'System');

INSERT INTO `company_locations`(`id`, `company_id`, `location_name`, `post_code`, `prefecture`, `city`, `home_address`, `tel`, `created_date`, `updated_date`, `created_by`, `updated_by`)
VALUES (1, 1, 'ABCビル', '150-0031', '東京都', '渋谷区', '桜丘町909-18', '000-00000-000', NOW(), NOW(), 'SYSTEM', 'SYSTEM');

INSERT INTO `recruitment_manuscripts`(`id`, `title`,`public_status`,`small_occupation_id`,`employment_status`,`introduction`,`summary`,`job_description`,`total_click`,`location_id`,`is_deleted`,`created_date`,`updated_date`,`created_by`,`updated_by`)
VALUES (1, 'エンジニアの求人','PUBLIC',10,'FULL_TIME','企業紹介','企業概要','求人内容',1,1,b'0',NOW(),NOW(),'SYSTEM','SYSTEM');

INSERT INTO `recruitment_manuscripts`(`id`, `title`,`public_status`,`small_occupation_id`,`employment_status`,`introduction`,`summary`,`job_description`,`total_click`,`location_id`,`is_deleted`,`created_date`,`updated_date`,`created_by`,`updated_by`)
VALUES (2, 'デザインの求人','PRIVATE',12,'CONTRACT','企業紹介','企業概要','求人内容',1,1,b'0',NOW(),NOW(),'SYSTEM','SYSTEM');

c. Add a success test case

@Transactional
@ActiveProfiles("test")
@SqlMergeMode(MERGE)
@Sql(scripts = {"/initial_data_1.0_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@SpringBootTest(classes = TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@RequiredArgsConstructor
public class JobPostControllerSuccessIntegrationTest {

@Autowired
private JobPostRepository postRepository;

@Nested
@DisplayName("求人詳細を取得するAPI: _getJobPostDetails")
class GetJobPostDetails {

@Test
@Sql("/jobPosts_1.0_setup.sql")
@DisplayName("Test get job post details when isClickCount=true success")
public void test_whenGetJobPostDetails_isClickCount_shouldReturn200() throws Exception {
Long postId = 11L;
MockHttpServletRequestBuilder mockRequest = MockMvcRequestBuilders.get("/media/manuscripts/" + manuscriptId + "?isClickCount=true")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON);

MvcResult result = mockMvc
.perform(mockRequest)
.andExpect(status().isOk()).andReturn();

String content = result.getResponse().getContentAsString((StandardCharsets.UTF_8));
DataResponse dataResponse = objectMapper.readValue(content, DataResponse.class);
JobPostDetailsDto actual = objectMapper.readValue(objectMapper.writeValueAsString(dataResponse.getData()), JobPostDetailsDto.class);

assertEquals(200, result.getResponse().getStatus());
assertEquals(String.format(SuccessMessage.GET_MSG.getDescription(), EntityName.POST), dataResponse.getMessage());

assertEquals(postId, actual.getId());
assertEquals("エンジニアの求人", actual.getTitle());
assertEquals("Welcome to SBI", actual.getIntroduction());
assertEquals("This is our job manuscript", actual.getSummary());
assertEquals("FULL_TIME", actual.getEmploymentStatus());
assertEquals("月給200000円〜月給500000円", actual.getSalary());
Optional<JobPost> jobPost= postRepository.findById(postId);
assert (jobPost).isPresent();

assertEquals(2, jobPost.get().getTotalClick());
}
}
}

2. Private Restful API

In the given example above, we also have one private URL which is /media/applicants/profile

Since it’s private, only logged in users of account type “APPLICANT” can call this API.

@PreAuthorize("hasAuthority('APPLICANT')")

a. Create new test class: ApplicantControllerSuccessIntegrationTest.java

b. Prepare test data set (Skipped since I don’t need more data here)

c. Simulate security layer

To do that, we’ll create a configuration file named: TestConfiguration.java

We will declare default accounts that simulate real users logged into the system.

@org.springframework.boot.test.context.TestConfiguration
public class TestConfiguration {

@Bean
@Primary
public UserDetailsService userDetailsService() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

final User.UserBuilder userBuilder = User.builder().passwordEncoder(passwordEncoder::encode);
List<GrantedAuthority> managerAuthorities = AuthorityUtils.createAuthorityList("MANAGER");
List<GrantedAuthority> applicantAuthorities = AuthorityUtils.createAuthorityList("APPLICANT");

UserDetails managerUser = userBuilder.username("project_manager@gmail.com").password(passwordEncoder.encode("password")).authorities(managerAuthorities).build();
UserDetails applicantUser = userBuilder.username("applicant_001@gmail.com").password(passwordEncoder.encode("password")).authorities(applicantAuthorities).build();

return new InMemoryUserDetailsManager(Arrays.asList(clientUser, agencyUser, saleUser, systemUser, applicantUser));
}
}

d. Add a success test case

@ActiveProfiles("test")
@Sql(scripts = {"/initial_data_1.0_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@SpringBootTest(classes = TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@RequiredArgsConstructor
public class ApplicantControllerSuccessIntegrationTest {

@Nested
@DisplayName("応募者のプロファイルを取得するAPI: _getApplicantProfile")
class GetApplicantProfile {

@Test
@WithUserDetails("applicant_001@gmail.com")
@DisplayName("Test whether to get profile of APPLICANT successfully")
public void test_whenGetApplicantProfile_byApplicant_shouldReturn200() throws Exception {
MockHttpServletRequestBuilder mockRequest = MockMvcRequestBuilders.get("/media/applicants/profile")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON);

MvcResult result = mockMvc
.perform(mockRequest)
.andExpect(status().isOk()).andReturn();
String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
DataResponse dataResponse = objectMapper.readValue(content, DataResponse.class);
ApplicantProfileDto actual = objectMapper.convertValue(dataResponse.getData(), ApplicantProfileDto.class);

assertEquals(200, result.getResponse().getStatus());
assertEquals("applicant_001@gmail.com", actual.getEmail());
assertEquals("青森 神社", actual.getFullName());
assertEquals("アオモリ ジンジャ", actual.getHiraganaName());
assertEquals("1995/08/13", actual.getBirthday());
assertEquals("東京都", actual.getPrefecture());
assertEquals("渋谷区", actual.getCity());
assertEquals("080-3945-6778", actual.getTel());
assertEquals(Gender.FEMALE.getId(), actual.getGender());
assertEquals(EmploymentStatusItemsDto.builder()
.id(EmploymentStatus.CONTRACT.getId())
.employmentStatusName(EmploymentStatus.CONTRACT.getName())
.build(),
actual.getEmploymentStatus());
}
}
}

III. Conclusion

There is still much to discuss on how to write the tests. It varies depending on the specific project like test scenarios, how much data is enough for a test case, etc.

Besides knowing how to write tests, it’s better to understand how many test cases we should write to cover all possible scenarios . I’ll have another article to share about this topic later.

I hope you enjoyed this article. Thank you for reading!

--

--