2017-09-03 6 views
0

J'ai une implémentation de l'interface Spring UserDetailsService:Je ne peux pas réécrire test unitaire Mockito à Spock Spécification

@Service 
public class UserDetailsServiceImpl implements UserDetailsService { 

    private final UserRepository userRepository; 

    @Autowired 
    public UserDetailsServiceImpl(UserRepository userRepository) { 
     this.userRepository = userRepository; 
    } 

    @Override 
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
     final UserEntity user = userRepository.findByUsername(username); 

     if (user == null) { 
      throw new UsernameNotFoundException("Cannot find user with username " + username); 
     } 

     return new User(user); 
    } 
} 

UserRepository est une interface standard extendng JpaRepository<UserEntity, Long>UserEntity est ma classe de modèle. Est une implémentation de UserDetails de Spring Framework.

Et j'ai écrit un test unitaire pour cette méthode en utilisant JUnit et Mockito. Ces tests fonctionnent:

@RunWith(SpringRunner.class) 
@DirtiesContext 
public class UserDetailsServiceTest { 

    @Autowired 
    private UserDetailsService userDetailsService; 

    @Test 
    public void shouldFindUser() throws Exception { 
     // given 
     final UserEntity user = new UserEntity(
       1L, 
       "username", 
       "[email protected]", 
       "password", 
       new ArrayList<>() // list of roles 
     ); 

     when(UserDetailsServiceTestContext.userRepository.findByUsername(user.getUsername())) 
       .thenReturn(user); 

     // when 
     final UserDetails result = userDetailsService.loadUserByUsername(user.getUsername()); 

     // then 
     assertThat(result).isEqualTo(UserFactory.create(user)); 
     verify(UserDetailsServiceTestContext.userRepository) 
       .findByUsername(user.getUsername()); 
    } 

    @Test(expected = UsernameNotFoundException.class) 
    public void shouldNotFindUser() throws Exception { 
     // given 
     when(UserDetailsServiceTestContext.userRepository.findByUsername(anyString())) 
       .thenReturn(null); 

     // when 
     final UserDetails result = userDetailsService.loadUserByUsername(new String()); 
    } 

    @TestConfiguration 
    static class UserDetailsServiceTestContext { 

     @MockBean 
     private static UserRepository userRepository; 

     @Bean 
     UserDetailsService userDetailsService() { 
      return new UserDetailsServiceImpl(userRepository); 
     } 
    } 

} 

Et maintenant j'essaye d'écrire ces tests en utilisant le framework Groovy et Spock. J'ai écrit la spécification suivante:

def 'should find user'() { 
     given: 
      def user = new UserEntity(
       1L, 
       "username", 
       "[email protected]", 
       "password" 
       new ArrayList<>() // list of roles 
      ) 

      userRepository.findByUsername(user.username) >> user 
      // userRepository.findByUsername(_ as String) >> user // also working 

     when: 
      def result = userDetailsService.loadUserByUsername(user.username) 

     then: 
      result == new User(user) 
    } 

et ce test fonctionne. Mais quand je veux vérifier l'appel de userRepository par ajouter dans la section then: une déclaration 1 * userRepository.findByUsername(user.username) ou 1 * userRepository.findByUsername(_ as String) j'obtiens une erreur UserDetailsServiceSpec.should find user and return new User:36 » UsernameNotFound. ligne 36 est dans la section when:

Répondre

1

Vous devez faire la stubbing et de vérification en une seule étape

then: 
1 * userRepository.findByUsername(user.username) >> user 

Pour plus de détails, voici ma réponse de Predefined mock response in Spock:

S'il vous plaît se référer à la documentation

Lorsque vous vous moquez d'un même appel de méthode et que vous le talonnez, ils doivent se produire dans la même interaction. En particulier, les éléments suivants fractionnement style Mockito de stubbing et se moquant dans deux états distincts ne fonctionnera pas:

setup: 
subscriber.receive("message1") >> "ok" 

when: 
publisher.send("message1") 

then: 
1 * subscriber.receive("message1") 

Comme expliqué dans Où déclarer les interactions, l'appel de réception va d'abord s'apparié contre l'interaction dans le bloc then. Puisque cette interaction ne spécifie pas de réponse, la valeur par défaut du type de retour de la méthode (null dans ce cas) sera retournée. (Ceci est juste une autre facette de l'approche indulgente de Spock à se moquer.). Par conséquent, l'interaction dans le setup: block n'aura jamais une chance de correspondre.


Lorsque vous traitez avec le printemps et l'opération proxy que vous pouvez également exécuter ce problème https://github.com/spockframework/spock/issues/758