Как проверить только одну часть метода, тестовая часть которой написана в тестовом классе с помощью spock - PullRequest
0 голосов
/ 27 марта 2020

У меня есть метод регистрации в классе обслуживания, и я пытаюсь написать модульный тест для успешного случая для предотвращения дублирования электронной почты

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 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)
    }


}

test не удалось, потому что он дает мне NPE для ByCryptPasswordEncoder: но я не пишу интеграционный тест, и мне приходится тестировать только повторные случаи успешной и неудачной отправки электронной почты

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)

Но я комментирую их в классе обслуживания

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

Как я могу решить эту проблему?

1 Ответ

0 голосов
/ 28 марта 2020

Хорошо, я еще немного посмотрел на ваш код и создал множество фиктивных классов для того, чтобы он компилировался и запускался, пытаясь выяснить, каков ваш вариант использования. На самом деле вы должны были показать в своем MCVE , но я думаю, у меня есть идея сейчас. (Благодаря COVID-19 мне стало скучно, потому что не было места для go и сегодняшних знакомых.)

Я вижу две насущные проблемы:

  1. Вы не можете определить взаимодействие 1 * entity.isPresent() >> false, потому что ваш entity не является имитатором или шпионом. Кроме того, вы используете приватный конструктор для Optional, чтобы инициализировать entity, что также ужасно и не будет работать за пределами Groovy. Кроме того, нет необходимости проверять, что isPresent() был вызван для необязательного объекта, только убедился, что вызов метода возвращает false. Этого можно добиться гораздо проще, просто написав def entity = Optional.empty() и удалив вместо этого взаимодействие.

  2. Как я уже сказал в комментарии, вы можете избавиться от 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 или выделение проверки пароля в отдельный метод также может быть полезно, если вы хотите смоделировать / заглушить кодировщик или его результат для некоторых тестов.

...