Object in bean constructor empty - spring-boot

I have exception-messages written down in the application.yml. They are pure text, which is later reformatted using java.text.MessageFormat.
I have got the following custom RuntimeException my service throws when login failed:
#Component
public class AccountLoginFailedException extends RuntimeException {
#Autowired
public AccountLoginFailedException(#Value("#(${authservice.exception-messages.login-failed})") final String message, #Qualifier(value = "Credentials") final Credentials credentials) {
super(MessageFormat.format(message, credentials.getUsername()));
}
}
My test, which solely tests the AccountController and mocks away the service behind it:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = AuthServiceTestConfiguration.class)
#WebMvcTest(AccountController.class)
public class AccountControllerTest {
#Autowired
private BeanFactory beanFactory;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private TestHelper helper;
#MockBean
private AccountService accountService;
#Autowired
private JwtService jwtService;
#Autowired
private MockMvc mvc;
#Test
public void test_LoginFailed_AccountDoesNotExist() throws Exception {
// Given
final Credentials credentials = helper.testCredentials();
final String credentialsJson = objectMapper.writeValueAsString(credentials);
final AccountLoginFailedException loginFailedException = beanFactory.getBean(AccountLoginFailedException.class, credentials);
// When
given(accountService.login(credentials)).willThrow(loginFailedException);
// Then
mvc
.perform(
post("/login")
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(credentialsJson))
.andExpect(status().isUnprocessableEntity())
.andExpect(jsonPath("$.data").value(equalTo(loginFailedException.getMessage())));
}
}
message contains the correct String. However: credentials contains just an empty object (not null) instead of the one created using helper.testCredentials().
Here is a slightly simplified TestHelper class I am using:
#TestComponent
public class TestHelper {
public static final String USERNAME = "SomeUsername";
public static final String PASSWORD = "SomePassword";
#Autowired
private BeanFactory beanFactory;
public Credentials testCredentials() {
final Credentials credentials = beanFactory.getBean(Credentials.class.getSimpleName(), Credentials.class);
credentials.setUsername(USERNAME);
credentials.setPassword(PASSWORD);
return credentials;
}
}
These custom exceptions are thrown by my application only and are always expected to contain the credentials (username) responsible for it. I also have a AccountExceptionsControllerAdvice-class, which just wraps these custom exceptions in a generic JSON response, exposing the error in a preferred manner.
How can I ensure that this particular instance of Credentials is inserted into the particular instance of AccountLoginFailedException? Or should I not be autowiring exceptions at all?

You could mock your Credentials component in your tests as follows:
#MockBean
private Credentials credentials;
#Before
public void before() {
when(credentials.getUsername()).thenReturn(USERNAME);
when(credentials.getPassword()).thenReturn(PASSWORD);
}

Related

How can I Integrate SpringSecuirty to My SpringBootTest?

I'm trying to test a comment_post method.
Comment has many - to - one relationship with User Entity which comes from Spring Security.
I connected this relationship by using Principal.
I think I made it working properly, but having trouble applying it to test.
Problem is that Comment Posting method gets user by finding User in Repository using Principal's email attribute, So I need to apply SecurityContext to test,
but I have no idea how to apply this function to test.
By Searching, I found out that I can make SpringSecurityContext by #WithSecurityContext
annotation, so I'm trying to apply it but having this error
java.lang.RuntimeException: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springboot.web.CommentsApiControllerTest$WithUserDetailsSecurityContextFactory': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'springboot.web.CommentsApiControllerTest' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I'm not even sure that my approach is correct.
tbh, I kind of feel lost, maybe it's because I'm new to SpringBoot, also Security.
Here's my codes.
CommentService
#RequiredArgsConstructor
#Service
public class CommentService {
private final CommentRepository commentRepository;
private final PostsRepository postsRepository;
private final UserDetailService userDetailService;
#Transactional
public Long commentSave(CommentSaveRequestDto requestDto, Long id) {
Posts post = postsRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다"));
requestDto.setPosts(post);
User user = userDetailService.returnUser();
requestDto.setUser(user);
return commentRepository.save(requestDto.toEntity()).getId();
}
`
UserDetailService
#RequiredArgsConstructor
#Service
public class UserDetailService {
private final UserRepository userRepository;
public User returnUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String userName;
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
int start = userName.indexOf("email")+6;
int end = userName.indexOf(".com,")+4;
String email = userName.substring(start, end);
User user = userRepository.findByEmail(email).orElse(null);
return user;
}
CommentSaveRequestDto
#Data
#NoArgsConstructor
#Builder
#AllArgsConstructor
public class CommentSaveRequestDto {
private String comment;
private Posts posts;
private User user;
/* Dto -> Entity */
public Comment toEntity() {
return Comment.builder()
.comment(comment)
.posts(posts)
.user(user)
.build();
}
}
And here is my CommentsApiControllrTest
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Transactional
public class CommentsApiControllerTest {
#LocalServerPort
private int port;
#Autowired
private PostsRepository postsRepository;
#Autowired
private CommentRepository commentRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private PostsService postsService;
#Autowired
private CommentService commentService;
#Autowired
private UserDetailService userDetailsService;
#Autowired
private WebApplicationContext context;
#Autowired ObjectMapper objectMapper;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.apply(sharedHttpSession())
.build();
}
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithUserDetailsSecurityContextFactory.class)
public #interface WithMockCustomUser {
String name() default "testName";
String email() default "testemail#gmail.com";
Role role() default Role.USER;
}
final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> {
private final UserDetailsService userDetailsService;
#Autowired
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public org.springframework.security.core.context.SecurityContext createSecurityContext(WithUserDetails withUser) {
String username = withUser.value();
Assert.hasLength(username, "value() must be non-empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
#After
public void tearDown() throws Exception {
postsRepository.deleteAll();
commentRepository.deleteAll();
}
#Test
#WithMockCustomUser
#Transactional // 프록시 객체에 실제 데이터를 불러올 수 있게 영속성 컨텍스트에서 관리
public void comment_등록() throws Exception {
// given
String title = "title";
String content = "content";
User user = userRepository.save(User.builder()
.name("name")
.email("fake#naver.com")
.picture("fakePic.com")
.role(Role.USER)
.build());
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.user(user)
.build();
postsRepository.save(requestDto.toEntity());
String comment = "comment";
Posts posts = postsRepository.findAll().get(0);
CommentSaveRequestDto saveRequestDto = CommentSaveRequestDto.builder()
.comment(comment)
.posts(posts)
.build();
Long id = posts.getId();
String url = "http://localhost:"+ port + "/api/posts/" + id + "/comments";
//when
mvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(saveRequestDto)))
.andExpect(status().isOk())
.andDo(print());
}
All I want is to make a mock Security User in test, so that
User user = userDetailService.returnUser();
this line in CommentService don't make any error.
Just a little tip would be really helpful to me.
Thank you in advance.

Spring boot test, #WithMockUser produce NPE in Controller method

i have an issue with my controller test
So my base test class configured like so
#SpringBootTest
#WithMockUser(username = "test_user",authorities = "Configured_allacess_authority")
public abstract class BaseControllerTest extends DatabaseIT {
protected static long counter;
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
protected ObjectMapper objectMapper;
protected MockRestServiceServer restServiceServer;
#Autowired
RestTemplate restTemplate;
#BeforeEach
protected void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.build();
restServiceServer = MockRestServiceServer.createServer(restTemplate);
}
and test code
mockMvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json(s));
When i debugging my controller method which is
public ResponseEntity<?> getSomething (
#Parameter(description = "ID") final String id,
#ApiIgnore #AuthenticationPrincipal Authentication user){
////
}
user value is null, though when i invoke SecurityContextHolder.getContext().getAuthentication() here in controller i'm getting Authentication object that's refers to one i mocked in
#WithMockUser(username = "test_user",authorities = "Configured_allacess_authority")
So recently i've updated my project to Java 17 , that also forced me to update spring boot to
version: '2.5.5'
and spring cloud to
"org.springframework.cloud:spring-cloud-dependencies:2020.0.5"

Spring boot rest api mockito + mockmvc persistence

I would like to create Test for my rest controller:
#Controller
#RequestMapping("/v2/api/show/project")
public class ApiAccessController {
private final ApiAccessService apiAccessService;
#Autowired
ApiAccessController(ApiAccessService apiAccessService){
this.apiAccessService = apiAccessService;
}
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id")Long id, Principal principal) {
return apiAccessService.generateApiKey(id, principal.getName());
}
}
My test looks as follow:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
Principal principal = new Principal() {
#Override
public String getName() {
return "TEST_PRINCIPAL";
}
};
#InjectMocks
ApiAccessController apiAccessController;
#Mock
ProjectRepository projectRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
Project project = new Project();
project.setId((long) 1);
project.setName("test");
project.setDescription("testdesc");
project.setCiid("ciid");
when(projectRepository.save(any(Project.class))).thenReturn(project);
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/" + project.getId() +"/apikey").principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Which is ment to create project and then run generateApiKey on this project, however I get NullpointerException looking like mocked controller cannot find created entity
could anyone please point me in the right direction as I am just starting with testing?
You should mock ApiAccessService instead of ProjectRepository.
Have a look at the code:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
private Principal principal = () -> "TEST_PRINCIPAL";
#InjectMocks
private ApiAccessController apiAccessController;
#Mock
private ApiAccessService apiAccessService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
long id = 1L;
when(apiAccessService.generateApiKey(id, principal.getName())).thenReturn(new ApiKeyResponse(111L));
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/{id}/apikey", id).principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
If you want to create integration test, that tests ApiAccessController -> ApiAccessService -> ProjectRepository integration you need to load your context (use for example #SpringBootTest).
Also you need to fix controller, use ResponseEntity.ok(...) :
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id") Long id, Principal principal) {
return ResponseEntity.ok(apiAccessService.generateApiKey(id, principal.getName()));
}
You can find really good examples of all test types in this repository MVC tests examples
The Mock you are creating is not referenced in the Controller. The Service you reference in the Controller is not part of your test setup. Therefore any access to the Service will cause a NullPointerException as the Service is not set.

Spring-Boot UnitTest: #Value in ConstraintValidator

I'm currently providing coverage - testing the validation of my DTO through MockMVC request call.
I recently introduced a new field in my Registration ConstraintValidator, supportedSpecializations, of which I inject the values from application.properties for easy maintenance and expandability. see code fragment below:
#Component
public class RegistrationValidator implements ConstraintValidator<Registration, String> {
//campus.students.supportedspecializations="J2E,.NET,OracleDB,MySQL,Angular"
#Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private String specializationExceptionMessage;
//All ExceptionMessages are maintained in a separate class
#Override
public void initialize(Registration constraintAnnotation) {
exceptionMessage = constraintAnnotation.regionException().getMessage();
}
#Override
public boolean isValid(RegistrationData regData, ConstraintValidatorContext context) {
String[] specializations = supportedSpecializations.split(",");
boolean isValidSpecialization = Arrays.stream(specializations)
.anyMatch(spec -> spec.equalsIgnoreCase(regData.getSpec()));
if (!isValidSpecialization){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(specializationExceptionMessage)
.addConstraintViolation();
return false;
}
//additional validation logic...
return true;
}
}
Unit tests now fail due to the field not being injected by the defined property of the #Value annotation.
I'm not sure if ReflectionTestUtils could help in my case, so any suggestions are greatly appreciated about how to inject the required values in UnitTests.
Spring version is 2.1.0
I'm currently testing with the following snippet:
#InjectMocks
private StudentController mockRestController;
#Mock
private StudentService mockStudentService;
#Mock
private ValidationExceptionTranslator mockExceptionTranslator;
#Value("${campus.students.supportedspecializations}")
private String supportedSpecializations;
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
#Test
public void testValidation_UnsupportedSpecialization() throws Exception {
MvcResult mvcResult = mockMvc.perform(
post("/Students").contentType(MediaType.APPLICATION_JSON_UTF8).content(
"{\"registrationData\":{\"spec\":\"unsupported\"}}"))
.andExpect(status().isBadRequest())
.andReturn();
assertEquals(VALIDATION_FAILED, mvcResult.getResponse().getContentAsString());
verify(mockExceptionTranslator, times(1)).translate(Mockito.any());
verify(mockStudentService, times(0)).insertStudent(Mockito.any());
}
I tried annotating my Test class with #RunWith(SpringRunner.class) and #SpringBootTest(classes = Application.class), but the validation test still fails due to #Value not being resolved. I might be wrong but I think the ConstraintValidator's instance is created before we reach the restController, so a MockMVC perform(...) call couldn't simply just make sure the appropriate #Value in the validator gets injected into supportedSpecializations.
Solved the issue the following way:
Added the following annotations to the Test Class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
Then autowired the controller and mockMVC, finally annotated service and translator with Spring's #MockBean
So currently it looks something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc
public class StudentValidatorTest {
#Autowired
private StudentController mockRestController;
#MockBean
private StudentService mockStudentService;
#MockBean
private ValidationExceptionTranslator mockExceptionTranslator;
#Autowired
private MockMvc mockMvc;
private static final String VALIDATION_SUCCESSFUL = "success";
private static final String VALIDATION_FAILED = "failed";
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(mockRestController).build();
doReturn(
ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "text/html; charset=utf-8")
.body(VALIDATION_SUCCESSFUL))
.when(mockStudentService).insertStudent(Mockito.any());
doReturn(
ResponseEntity.status(HttpStatus.BAD_REQUEST)
.header("Content-Type", "application/json")
.body(VALIDATION_FAILED))
.when(mockExceptionTranslator).translate(Mockito.any());
}
//...and tests...
Yes,
use ReflectionTestUtil.
Use ReflectionTestUtil.setField to set the value of supportedSpecializationsin the
setup() method (junit < 1.4)
or in the #Before annotated method (junit > 1.4) in your unit test.
More Details
I recommend against using MockMVC for your unit tests;
it is fine for integration tests,
just not unit tests.
There is no need to start Spring for a Unit Test;
you never need Spring to perform injections for your unit tests.
Instead,
instantiate the class you are testing and call the methods directly.
Here is a simple example:
public class TestRegistrationValidator
{
private static final String VALUE_EXCEPTION_MESSAGE = "VALUE_EXCEPTION_MESSAGE";
private static final String VALUE_SUPPORTED_SPECIALIZATIONS = "BLAMMY,KAPOW";
private RegistrationValidator classToTest;
#Mock
private Registration mockRegistration;
#Mock
private RegionExceptionType mockRegionExceptionType; // use the actual type of regionExcpeption.
#Before
public void preTestSetup()
{
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(classToTest, "supportedSpecializations", VALUE_SUPPORTED_SPECIALIZATIONS);
doReturn(VALUE_EXCEPTION_MESSAGE).when(mockRegionExceptionType).getMessage();
doReturn(mockRegionExceptionType).when(mockRegion).regionException();
}
#Test
public void initialize_allGood_success()
{
classToTest.initialize(mockRegistration);
...assert some stuff.
...perhaps verify some stuff.
}
}
The best option i think for you is to use constructor injection in your RegistrationValidator.class , so that you can assign mock or test values directly for testing as well when required. Example :
#Component
class ExampleClass {
final String text
// Use #Autowired to get #Value to work.
#Autowired
ExampleClass(
// Refer to configuration property
// app.message.text to set value for
// constructor argument message.
#Value('${app.message.text}') final String text) {
this.text = text
}
}
This way you can set your mock values to the variables for unit testing.
Yes, you are right a custom constructor is not an option here, then you could introduce a configuration class where you have these values read from yml or property and autowire that in the validator .That should work for you.
Or
You can provide the #Value properties in a separate test.yml or test.properties and specify that to be taken up while running your integrated tests. In that case you should be able to resolve these values.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ExampleApplication.class)
#TestPropertySource(locations="classpath:test.properties")
public class ExampleApplicationTests {
}
The #TestPropertySource annotation has higher precedence order and it should resolve your values.

Using #RestClientTest in spring boot test

I want to write a simple test using #RestClientTest for the component below (NOTE: I can do it without using #RestClientTest and mocking dependent beans which works fine.).
#Slf4j
#Component
#RequiredArgsConstructor
public class NotificationSender {
private final ApplicationSettings settings;
private final RestTemplate restTemplate;
public ResponseEntity<String> sendNotification(UserNotification userNotification)
throws URISyntaxException {
// Some modifications to request message as required
return restTemplate.exchange(new RequestEntity<>(userNotification, HttpMethod.POST, new URI(settings.getNotificationUrl())), String.class);
}
}
And the test;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}
But i get error that No qualifying bean of type 'org.springframework.web.client.RestTemplate' available. Which is right of course as i havn't mocked it or used #ContextConfiguration to load it.
Isn't #RestClientTest configures a RestTemplate? or i have understood it wrong?
Found it! Since i was using a bean that has a RestTemplate injected directly, we have to add #AutoConfigureWebClient(registerRestTemplate = true) to the test which solves this.
This was in the javadoc of #RestClientTest which i seem to have ignored previously.
Test which succeeds;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
#AutoConfigureWebClient(registerRestTemplate = true)
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}

Resources