Хорошо, я еще немного посмотрел на ваш код и создал множество фиктивных классов для того, чтобы он компилировался и запускался, пытаясь выяснить, каков ваш вариант использования. На самом деле вы должны были показать в своем MCVE , но я думаю, у меня есть идея сейчас. (Благодаря COVID-19 мне стало скучно, потому что не было места для go и сегодняшних знакомых.)
Я вижу две насущные проблемы:
Вы не можете определить взаимодействие 1 * entity.isPresent() >> false
, потому что ваш entity
не является имитатором или шпионом. Кроме того, вы используете приватный конструктор для Optional
, чтобы инициализировать entity
, что также ужасно и не будет работать за пределами Groovy. Кроме того, нет необходимости проверять, что isPresent()
был вызван для необязательного объекта, только убедился, что вызов метода возвращает false
. Этого можно добиться гораздо проще, просто написав def entity = Optional.empty()
и удалив вместо этого взаимодействие.
Как я уже сказал в комментарии, вы можете избавиться от NullPointerException
для вызова шифровщика просто убедившись, что DTO имеет пароль, установленный через userDto.setPassword("pw")
или аналогичный. Ваш тест будет выглядеть так:
package de.scrum_master.stackoverflow.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(..)
действительно вызывается и что он вызывается с указанием c параметр. Я думаю, что это завышенная спецификация для юнит-теста. Вы должны будете отрегулировать его при изменении внутренней реализации тестируемого метода. Я думаю, что здесь должно быть достаточно просто указать результат заглушки. Если вы также немного реорганизуете код, тест будет выглядеть следующим образом:
package de.scrum_master.stackoverflow.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 * ...
взаимодействием.
PS: Я все еще думаю, что ваш тестируемый метод выиграет от его рефакторинга в меньшие методы который потом можно легко заглушить и / или проверить отдельно. Внедрение зависимостей для BCryptPasswordEncoder
или выделение проверки пароля в отдельный метод также может быть полезно, если вы хотите смоделировать / заглушить кодировщик или его результат для некоторых тестов.