如何只测试方法的一部分,该部分的测试是用 spock 在测试 class 中编写的
How to test only one part of method which part's test is written in test class with spock
我在服务中有注册方法 class 我尝试为成功案例编写单元测试以防止重复电子邮件
public void signUp(UserDTO userDTO) {
logger.info("ActionLog.Sign up user.Start");
Optional<UserEntity> checkedEmail = userRepository.findByEmail(userDTO.getEmail());
System.out.println(checkedEmail);
if (checkedEmail.isPresent()) {
System.out.println("check email: "+checkedEmail);
logger.error("ActionLog.WrongDataException.Thrown");
throw new WrongDataException("This email already exists");
}
String password = new BCryptPasswordEncoder().encode(userDTO.getPassword());
UserEntity customerEntity = UserEntity
.builder()
.name(userDTO.getName())
.surname(userDTO.getSurname())
.username(userDTO.getEmail())
.email(userDTO.getEmail())
.password(password)
.role(Role.ROLE_USER)
.build();
userRepository.save(customerEntity);
logger.info("ActionLog.Sign up user.Stop.Success");
}
这是我的测试class
class UserServiceImplTest extends Specification {
UserRepository userRepository
AuthenticationServiceImpl authenticationService
UserServiceImpl userService
def setup() {
userRepository = Mock()
authenticationService = Mock()
userService = new UserServiceImpl(userRepository, authenticationService)
}
def "doesn't throw exception if email doesn't exist in database"() {
given:
def userDto = new UserDTO()
def entity = new Optional<UserEntity>()
userDto.setEmail("example@mail.ru")
1 * userRepository.findByEmail(userDto.getEmail()) >> entity
1 * entity.isPresent() >> false
when: "send dto object to service "
userService.signUp(userDto)
then: ""
notThrown(WrongDataException)
}
}
测试失败,因为它给了我 ByCryptPasswordEncoder 的 NPE:
但我不写集成测试,我只需要测试重复的电子邮件成功和失败案例
java.lang.NullPointerException
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.encode(BCryptPasswordEncoder.java:108)
at az.gdg.msauth.service.impl.UserServiceImpl.signUp(UserServiceImpl.java:41)
at az.gdg.msauth.service.UserServiceImplTest.doesn't throw exception if email doesn't exist in database(UserServiceImplTest.groovy:35)
但是,我在服务中评论这些class
String password = new BCryptPasswordEncoder().encode(userDTO.getPassword());
UserEntity customerEntity = UserEntity
.builder()
.name(userDTO.getName())
.surname(userDTO.getSurname())
.username(userDTO.getEmail())
.email(userDTO.getEmail())
.password(password)
.role(Role.ROLE_USER)
.build();
userRepository.save(customerEntity);
它给了我
Too few invocations for:
1 * entity.isPresent() >> false (0 invocations)
Unmatched invocations (ordered by similarity):
None
Too few invocations for:
1 * entity.isPresent() >> false (0 invocations)
Unmatched invocations (ordered by similarity):
None
我该如何解决这个问题?
好的,我进一步查看了您的代码并在本地创建了很多虚拟 类 以使其编译和 运行,试图弄清楚您的用例是什么。实际上你应该在你的 MCVE 中展示,但我想我现在有想法了。 (感谢 COVID-19,我很无聊,因为今天没有地方去见朋友。)
我看到两个紧迫的问题:
您不能定义交互 1 * entity.isPresent() >> false
因为您的 entity
不是模拟或间谍。此外,您使用 Optional
的私有构造函数来初始化 entity
,这也很丑陋并且不会在 Groovy 之外工作。此外,没有必要检查是否在可选对象上调用了 isPresent()
,只需确保方法调用 returns false
。只需编写 def entity = Optional.empty()
并删除交互即可轻松实现此目的。
正如我在评论中所说,您只需确保 DTO 具有通过 userDto.setPassword("pw")
或相似的。您的测试将如下所示:
package de.scrum_master.Whosebug.q60884910
import spock.lang.Specification
class UserServiceImplTest extends Specification {
UserRepository userRepository
AuthenticationServiceImpl authenticationService
UserServiceImpl userService
def setup() {
userRepository = Mock()
authenticationService = Mock()
userService = new UserServiceImpl(userRepository, authenticationService)
}
def "doesn't throw exception if email doesn't exist in database"() {
given:
def userDto = new UserDTO()
def entity = Optional.empty()
userDto.setEmail("example@mail.ru")
userDto.setPassword("pw")
1 * userRepository.findByEmail(userDto.getEmail()) >> entity
// 1 * entity.isPresent() >> false
when: "send dto object to service "
userService.signUp(userDto)
then: ""
notThrown(WrongDataException)
}
}
我也认为没有必要检查 userRepository.findByEmail(..)
是否实际被调用并且它是用特定参数调用的。我认为这是单元测试的过度规范。在更改被测方法的内部实现时,您必须调整它。我认为这里只需指定存根结果就足够了。如果你也重新组织一下代码,测试看起来像这样:
package de.scrum_master.Whosebug.q60884910
import spock.lang.Specification
class UserServiceImplTest extends Specification {
def userRepository = Stub(UserRepository)
def authenticationService = Stub(AuthenticationServiceImpl)
def userService = new UserServiceImpl(userRepository, authenticationService)
def "doesn't throw exception if email doesn't exist in database"() {
given: "a user DTO"
def userDto = new UserDTO()
userDto.email = "example@mail.ru"
userDto.password = "pw"
and: "a user repository not finding any user by e-mail"
userRepository.findByEmail(_) >> Optional.empty()
when: "signing up a new user"
userService.signUp(userDto)
then: "no duplicate e-mail address exception is thrown"
notThrown WrongDataException
}
}
请注意,我将 Mock()
更改为 Stub()
,因为我们不再检查任何交互(调用次数)。如果您觉得 userRepository
需要这个,您可以将其还原为再次与 1 * ...
交互的模拟。
P.S.: 我仍然认为您的被测方法可以通过将其重构为更小的方法而获益,然后您可以轻松地对 and/or 单独进行测试。 BCryptPasswordEncoder
的依赖注入或将密码检查分解为一个单独的方法也可能会有所帮助,如果你想 mock/stub 编码器或其某些测试的结果。
我在服务中有注册方法 class 我尝试为成功案例编写单元测试以防止重复电子邮件
public void signUp(UserDTO userDTO) {
logger.info("ActionLog.Sign up user.Start");
Optional<UserEntity> checkedEmail = userRepository.findByEmail(userDTO.getEmail());
System.out.println(checkedEmail);
if (checkedEmail.isPresent()) {
System.out.println("check email: "+checkedEmail);
logger.error("ActionLog.WrongDataException.Thrown");
throw new WrongDataException("This email already exists");
}
String password = new BCryptPasswordEncoder().encode(userDTO.getPassword());
UserEntity customerEntity = UserEntity
.builder()
.name(userDTO.getName())
.surname(userDTO.getSurname())
.username(userDTO.getEmail())
.email(userDTO.getEmail())
.password(password)
.role(Role.ROLE_USER)
.build();
userRepository.save(customerEntity);
logger.info("ActionLog.Sign up user.Stop.Success");
}
这是我的测试class
class UserServiceImplTest extends Specification {
UserRepository userRepository
AuthenticationServiceImpl authenticationService
UserServiceImpl userService
def setup() {
userRepository = Mock()
authenticationService = Mock()
userService = new UserServiceImpl(userRepository, authenticationService)
}
def "doesn't throw exception if email doesn't exist in database"() {
given:
def userDto = new UserDTO()
def entity = new Optional<UserEntity>()
userDto.setEmail("example@mail.ru")
1 * userRepository.findByEmail(userDto.getEmail()) >> entity
1 * entity.isPresent() >> false
when: "send dto object to service "
userService.signUp(userDto)
then: ""
notThrown(WrongDataException)
}
}
测试失败,因为它给了我 ByCryptPasswordEncoder 的 NPE: 但我不写集成测试,我只需要测试重复的电子邮件成功和失败案例
java.lang.NullPointerException
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.encode(BCryptPasswordEncoder.java:108)
at az.gdg.msauth.service.impl.UserServiceImpl.signUp(UserServiceImpl.java:41)
at az.gdg.msauth.service.UserServiceImplTest.doesn't throw exception if email doesn't exist in database(UserServiceImplTest.groovy:35)
但是,我在服务中评论这些class
String password = new BCryptPasswordEncoder().encode(userDTO.getPassword());
UserEntity customerEntity = UserEntity
.builder()
.name(userDTO.getName())
.surname(userDTO.getSurname())
.username(userDTO.getEmail())
.email(userDTO.getEmail())
.password(password)
.role(Role.ROLE_USER)
.build();
userRepository.save(customerEntity);
它给了我
Too few invocations for:
1 * entity.isPresent() >> false (0 invocations)
Unmatched invocations (ordered by similarity):
None
Too few invocations for:
1 * entity.isPresent() >> false (0 invocations)
Unmatched invocations (ordered by similarity):
None
我该如何解决这个问题?
好的,我进一步查看了您的代码并在本地创建了很多虚拟 类 以使其编译和 运行,试图弄清楚您的用例是什么。实际上你应该在你的 MCVE 中展示,但我想我现在有想法了。 (感谢 COVID-19,我很无聊,因为今天没有地方去见朋友。)
我看到两个紧迫的问题:
您不能定义交互
1 * entity.isPresent() >> false
因为您的entity
不是模拟或间谍。此外,您使用Optional
的私有构造函数来初始化entity
,这也很丑陋并且不会在 Groovy 之外工作。此外,没有必要检查是否在可选对象上调用了isPresent()
,只需确保方法调用 returnsfalse
。只需编写def entity = Optional.empty()
并删除交互即可轻松实现此目的。正如我在评论中所说,您只需确保 DTO 具有通过
userDto.setPassword("pw")
或相似的。您的测试将如下所示:
package de.scrum_master.Whosebug.q60884910
import spock.lang.Specification
class UserServiceImplTest extends Specification {
UserRepository userRepository
AuthenticationServiceImpl authenticationService
UserServiceImpl userService
def setup() {
userRepository = Mock()
authenticationService = Mock()
userService = new UserServiceImpl(userRepository, authenticationService)
}
def "doesn't throw exception if email doesn't exist in database"() {
given:
def userDto = new UserDTO()
def entity = Optional.empty()
userDto.setEmail("example@mail.ru")
userDto.setPassword("pw")
1 * userRepository.findByEmail(userDto.getEmail()) >> entity
// 1 * entity.isPresent() >> false
when: "send dto object to service "
userService.signUp(userDto)
then: ""
notThrown(WrongDataException)
}
}
我也认为没有必要检查 userRepository.findByEmail(..)
是否实际被调用并且它是用特定参数调用的。我认为这是单元测试的过度规范。在更改被测方法的内部实现时,您必须调整它。我认为这里只需指定存根结果就足够了。如果你也重新组织一下代码,测试看起来像这样:
package de.scrum_master.Whosebug.q60884910
import spock.lang.Specification
class UserServiceImplTest extends Specification {
def userRepository = Stub(UserRepository)
def authenticationService = Stub(AuthenticationServiceImpl)
def userService = new UserServiceImpl(userRepository, authenticationService)
def "doesn't throw exception if email doesn't exist in database"() {
given: "a user DTO"
def userDto = new UserDTO()
userDto.email = "example@mail.ru"
userDto.password = "pw"
and: "a user repository not finding any user by e-mail"
userRepository.findByEmail(_) >> Optional.empty()
when: "signing up a new user"
userService.signUp(userDto)
then: "no duplicate e-mail address exception is thrown"
notThrown WrongDataException
}
}
请注意,我将 Mock()
更改为 Stub()
,因为我们不再检查任何交互(调用次数)。如果您觉得 userRepository
需要这个,您可以将其还原为再次与 1 * ...
交互的模拟。
P.S.: 我仍然认为您的被测方法可以通过将其重构为更小的方法而获益,然后您可以轻松地对 and/or 单独进行测试。 BCryptPasswordEncoder
的依赖注入或将密码检查分解为一个单独的方法也可能会有所帮助,如果你想 mock/stub 编码器或其某些测试的结果。