I am trying to write a unit test (using Mockito) for my Spring Security config class and it has this code
@Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
userAttributes.getOrDefault("field", List.of())).forEach(a-> mappedAuthorities.add( new SimpleGrantedAuthority(a)));
}
});
return mappedAuthorities;
};
}
And this is my unit test method which does not cover any line of above code.
@Before(){
this.config = new Config(); //this is Config class which has authoritiesMapper
}
@Test
public void testMapper(){
//I had some mockito code here but nothing worked.
this.config.authoritiesMapper();
}
If I write any test case, nothing invokes whole method, maybe because of lambda expression. How to write a test when lambda expression like this exist?
3
Lambdas defer execution and are not part of the regular code flow. The body of a lambda expression is only executed when you invoke the lambda. Lambdas map to SAM-types (single abstract method), and if you look at GrantedAuthoritiesMapper
, it has a single method: mapAuthorities
.
The lambda syntax is more or less syntactic sugar for returning an anonymous class instance (the reality is more complicated than that, but it suffices here as a simplified explanation):
@Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
return new GrantedAuthoritiesMapper() {
@Override
public Collection<? extends GrantedAuthority> mapAuthorities(
final Collection<? extends GrantedAuthority> authorities) {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
userAttributes.getOrDefault("field", List.of())).forEach(a-> mappedAuthorities.add( new SimpleGrantedAuthority(a)));
}
});
}
};
}
So if you want the body of the lambda to be called, then call it:
final Collection<? extends GrantedAuthority> authorities = ...;
this.config.authoritiesMapper().mapAuthorities(authorities); // invoke lambda
PS. Note that your mapper heavily relies on side-effects. I would suggest to either use regular for
loops if you want to follow the imperative paradigm or switch to a functional approach:
return authorities -> authorities.stream()
.filter(OAuth2UserAuthority.class::isInstance)
.map(OAuth2UserAuthority.class::cast)
.map(OAuth2UserAuthority::getAttributes)
.map(attrs -> attrs.get("field"))
.filter(Objets::nonNull)
.flatMap(Collection::stream)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());