Mockito를 사용해 Spring Service를 테스트 해보자


환경

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


Mockito를 통한 단위 테스트

Mockito

  • Mockito: Java에서 모킹(Mocking)을 지원해주는 테스트 프레임워크
  • @Mock: 테스트에 필요한 객체를 모킹(Mocking)해주는 어노테이션
  • @InjectMocks: 테스트에 필요한 객체를 모킹(Mocking)해주는것과 함께 @Mock으로 모킹된 객체들 중에서 필요한 객체들을 주입해주는 어노테이션
  • when(method_name).thenReturn(return_value);: 모킹(Mocking)된 객체의 특정 메소드(method_name) 호출시 return_value를 반환하도록 설정 가능한 메소드


예제

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testImplementation('org.mockito:mockito-core')
	testImplementation('org.mockito:mockito-junit-jupiter')
	testImplementation('org.hamcrest:hamcrest')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
    Boolean existsByEmail(String email);
}

Service

@Slf4j
@Service
public class UserService {

    @Autowired
    UserRepository userRepository;

    public UserEntity create(final UserEntity userEntity){
        if (userEntity == null || userEntity.getEmail() == null) {
            throw new RuntimeException("Invalid Arguments");
        }

        final String email = userEntity.getEmail();
        if (userRepository.existsByEmail(email)) {
            log.warn("Email already exists {}", email);
            throw new RuntimeException("Email already exists");
        }

        return userRepository.save(userEntity);
    }

}

Test

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void createTest() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(null);
        });

        UserEntity userEntityForNullEmail = UserEntity.builder().build();
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForNullEmail);
        });

        UserEntity userEntityForExistUser = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        when(userRepository.existsByEmail(any())).thenReturn(true);
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForExistUser);
        });

        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        reset(userRepository);
        when(userRepository.save(any())).thenReturn(userEntity);
        UserEntity returnedUserEntity = userService.create(userEntity);
        Assertions.assertEquals("test@test.com", returnedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", returnedUserEntity.getUsername());
        Assertions.assertEquals("testPassword", returnedUserEntity.getPassword());
    }
}


참고자료

Test Spring Service Using Mockito


Environment and Prerequisite

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


Mockito

Mockito

  • Mockito: Java test framework which supports mocking
  • @Mock: Object mocking annotation for test
  • @InjectMocks: Object mocking annotation with inject all needed objects with @Mock annotation for test
  • when(method_name).thenReturn(return_value);: When call mocking object’s specific method(method_name), this method make it to return return_value


Example

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testImplementation('org.mockito:mockito-core')
	testImplementation('org.mockito:mockito-junit-jupiter')
	testImplementation('org.hamcrest:hamcrest')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
    Boolean existsByEmail(String email);
}

Service

@Slf4j
@Service
public class UserService {

    @Autowired
    UserRepository userRepository;

    public UserEntity create(final UserEntity userEntity){
        if (userEntity == null || userEntity.getEmail() == null) {
            throw new RuntimeException("Invalid Arguments");
        }

        final String email = userEntity.getEmail();
        if (userRepository.existsByEmail(email)) {
            log.warn("Email already exists {}", email);
            throw new RuntimeException("Email already exists");
        }

        return userRepository.save(userEntity);
    }

}

Test

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void createTest() {
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(null);
        });

        UserEntity userEntityForNullEmail = UserEntity.builder().build();
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForNullEmail);
        });

        UserEntity userEntityForExistUser = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        when(userRepository.existsByEmail(any())).thenReturn(true);
        Assertions.assertThrows(RuntimeException.class, () -> {
            userService.create(userEntityForExistUser);
        });

        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        reset(userRepository);
        when(userRepository.save(any())).thenReturn(userEntity);
        UserEntity returnedUserEntity = userService.create(userEntity);
        Assertions.assertEquals("test@test.com", returnedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", returnedUserEntity.getUsername());
        Assertions.assertEquals("testPassword", returnedUserEntity.getPassword());
    }
}


Reference

@DataJpaTest를 사용해 JPA Repository를 테스트 해보자


환경

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


@DataJpaTest

@DataJpaTest

  • JPA에 관련된 요소들만 테스트하기 위한 어노테이션으로 JPA 테스트에 관련된 설정들만 적용해준다.
  • 메모리상에 내부 데이터베이스를 생성하고 @Entity 클래스들을 등록하고 JPA Repository 설정들을 해준다. 각 테스트마다 테스트가 완료되면 관련한 설정들은 롤백된다.


예제

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
}

Test

@ExtendWith(SpringExtension.class)
@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void saveTest() {
        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();

        UserEntity savedUserEntity = userRepository.save(userEntity);

        Assertions.assertEquals("test@test.com", savedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", savedUserEntity.getUsername());
    }

    @Test
    public void findByEmailTest() {
        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        UserEntity savedUserEntity = userRepository.save(userEntity);

        UserEntity foundUserEntity = userRepository.findByEmail(userEntity.getEmail());

        Assertions.assertEquals("test@test.com", savedUserEntity.getEmail());
        Assertions.assertEquals("test@test.com", foundUserEntity.getEmail());
    }
}


참고자료

Test JPA Repository using @DataJpaTest


Environment and Prerequisite

  • Java
  • Spring
  • JUnit5
  • SpringExtension.class


@DataJpaTest

@DataJpaTest

  • This annotation applies only configuration relevant to JPA tests.
  • It will configure an in-memory embedded database, scan @Entity classes and configure Spring Data JPA repositories. Setting is rollbacked at the end of each test.


Example

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.13'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'org.twpower'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation('org.springframework.boot:spring-boot-starter-data-jpa')
	implementation('org.springframework.boot:spring-boot-starter-web')

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'

	runtimeOnly 'com.h2database:h2'

	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.junit.jupiter:junit-jupiter')
	testImplementation('org.junit.jupiter:junit-jupiter-api')
	testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}

test {
	useJUnitPlatform()
}

Entity

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class UserEntity {
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;
}

JPA Repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    UserEntity findByEmail(String email);
}

Test

@ExtendWith(SpringExtension.class)
@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void saveTest() {
        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();

        UserEntity savedUserEntity = userRepository.save(userEntity);

        Assertions.assertEquals("test@test.com", savedUserEntity.getEmail());
        Assertions.assertEquals("testUsername", savedUserEntity.getUsername());
    }

    @Test
    public void findByEmailTest() {
        UserEntity userEntity = UserEntity.builder()
                .email("test@test.com")
                .username("testUsername")
                .password("testPassword")
                .build();
        UserEntity savedUserEntity = userRepository.save(userEntity);

        UserEntity foundUserEntity = userRepository.findByEmail(userEntity.getEmail());

        Assertions.assertEquals("test@test.com", savedUserEntity.getEmail());
        Assertions.assertEquals("test@test.com", foundUserEntity.getEmail());
    }
}


Reference

Mockito Mock 객체 초기화하기


환경

  • Java
  • Mockito


org.mockito.Mockito.reset

reset 메소드

    ...
    @Mock
    private UserRepository userRepository;
    ...
    reset(userRepository);
    ...


참고자료