Search code examples
javaunit-testingspock

Mocking objects with Spock


I am learning Spock framework. I have trouble with implementing some test to my project.

I write test to REST API save method :

@Override
public User save(User user) {
    roleRepository.findByName("ROLE_USER").ifPresent(role -> 
user.setRoles(Collections.singletonList(role)));
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    return userRepository.save(user);
}

And my test class looks like this :

class UserServiceImplSpec extends Specification {

 def userService
 def userRepository = Mock(UserRepository)
 def passwordEncoder = Mock(PasswordEncoder)
 def roleRepository = Mock(RoleRepository)

 def setup() {
    userService = new UserServiceImpl(userRepository, passwordEncoder, roleRepository)
}    


def 'should save user'() {
    given:
    def user = new User()

    when:
    userService.save(user)

    then:
    1 * roleRepository.findByName('ROLE_USER')
    1 * passwordEncoder.encode(user.getPassword())
    1 * userRepository.save(user)
    0 * _
}
}

After running test I have error:

java.lang.NullPointerException
at com.jakub.shop.service.impl.UserServiceImpl.save(UserServiceImpl.java:28)
at com.jakub.shop.service.impl.UserServiceImplSpec.should save user(UserServiceImplSpec.groovy:47)

I know that i have to mock something into my roleRepository, but I dont have a clue what. Propably list of roles. I tried in many ways but it did not work. What should I do to write this test correct? I know that my rest method is written properly.


Solution

  • You're not returning anything from your findByName or encode calls. You should be returning appropriate values and checking that they're used properly, something like this:

    def 'should save user'() {
        given:
        def user = new User()
        final String encodedPassword = 'pAsSwOrD'
        User saved
    
        when:
        userService.save(user)
    
        then:
        1 * roleRepository.findByName('ROLE_USER') >> Optional.empty()
        1 * passwordEncoder.encode(user.password) >> encodedPassword
        1 * userRepository.save(_) >> { saved = it[0] }
        0 * _
    
        and:
        encodedPassword == saved.password
        saved.roles.empty
    }
    

    Note also that you don't need a setup method; you can just say

    @Subject
    UserServiceImpl userService = new UserServiceImpl(userRepository, passwordEncoder, roleRepository)
    

    Even better, you might want to take a look at the documentation for data-driven testing and have multiple test cases like this:

    where:
    userRole || expected
    null     || emptyList()
    ROLE_OBJ || [ROLE_OBJ]