Spring Boot Unit Testing with WebMvcTest + JWT + AuthenticationPrinciple - spring-boot

I have a project with Spring-Boot + Spring-Security. In this project, typical I have MVC architecture.
My controller looks like:
#RestController
DemoController
{
public ResponseEntity<?> get(#AuthenticationPrinciple UserPrinciple userPrinciple)
{
return ...
}
}
I want to test this controller class with WebMvcTest. I know that I can handle it with SpringBootTest easily but SpringBootTest is not time-effective...
I've created a test class like:
#WebMvcTest(controllers = {DemoController.class})
DemoControllerTest
{
#Autowired
private MockMvc mockMvc;
#Test
void test_get()
{
this.mockMvc.perform(get("/...")
.header(AUTH_HEADER, getAuthorizationHeader())
.andExpect(status().isOk());
}
}
But I got error like userPrinciple is null or org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.sha.springbootbookseller.security.CustomUserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. How I can inject AuthenticationPrinciple in WebMvcTest?
Example Code: https://github.com/senolatac/spring-boot-book-seller
Test Class: https://github.com/senolatac/spring-boot-book-seller/blob/master/src/test/java/com/sha/springbootbookseller/controller/PurchaseHistoryControllerTest.java

Solved it with adding:
#WithMockUser
#ContextConfiguration
and mocking
#MockBean
private CustomUserDetailsService customUserDetailsService;
Final result looks like:
#WithMockUser
#WebMvcTest(controllers = {DemoController.class})
#ContextConfiguration(classes = {DemoController.class, JwtProvider.class, JwtFilter.class})
DemoControllerTest
{
#Autowired
private MockMvc mockMvc;
#MockBean
private CustomUserDetailsService customUserDetailsService;
#Test
void test_get()
{
this.mockMvc.perform(get("/...")
.header(AUTH_HEADER, getAuthorizationHeader())
.andExpect(status().isOk());
}
}
you can find the example code from: https://github.com/senolatac/spring-boot-book-seller

Related

java.lang.AssertionError: Status Expected :200 Actual :500

I wrote a small code to test my Controller and used mockito in this regard. The code is shown below with correct syntaxes
public class ApiTest {
private MockMvc mockMvc;
#InjectMocks
private PersonController personController;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(personController).build();
}
#Test
public void testPersonController() throws Exception{
//String jsonResult = "{\"name\": \"Müller\", \"lastName\": \"Hans\", \"zipCode\": \"67742\", \"city\": \"Lauterecken\", \"color\": \"blau\", \"id\": 1}";
mockMvc.perform(MockMvcRequestBuilders.
get("/api/v1/person/persons/1").
accept(MediaType.APPLICATION_JSON)).
andDo(print()).
andExpect(status().isOk()).
andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")).
andExpect(MockMvcResultMatchers.jsonPath("$name").value("Müller"))
.andExpect(MockMvcResultMatchers.jsonPath("$lastName").value("Hans")).
andExpect(MockMvcResultMatchers.jsonPath("$zipCode").value("67742"))
.andExpect(MockMvcResultMatchers.jsonPath("$city").value("Lauterecken"))
.andExpect(MockMvcResultMatchers.jsonPath("$color").value("blau"));
}
}
I am receiving an assertion error as shown below
java.lang.AssertionError: Status
Expected :200
Actual :500
Can anyone help me where I am going wrong?
Looks like you're not fully utilizing what Spring Boot offers to test controllers.
When you're testing with MockMvc:
annotate your class with #WebMvcTest
use #MockBean to mock any collaborators this controller depends on
autowire MockMvc instead of creating it
#WebMvcTest(PersonController.class)
public class ApiTest {
#Autowired
private MockMvc mockMvc;
#MockBean
SomeService someService; // only if controller has dependencies on any components
#Test
void aTest() {
mockMvc.perform(...)
// ...
}

Spring Boot integration test fails with "No qualifying bean" for MockMvc

I'd like to create an integration test for my Spring Boot application checking that a controller returns the correct HTTP status when sending an email.
This is how my test looks like:
#SpringBootTest
#RunWith(SpringRunner.class)
#AutoConfigureMockMvc
#Profile("test")
public class EmailControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Rule
public SmtpServerRule smtpServerRule = new SmtpServerRule(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
final EmailNotification emailNotification = EmailNotification.builder()
.emailAddress("foo#bar.com")
.subject("TEST_SUBJECT")
.content("TEST_CONTENT")
.build();
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content(objectMapper.writeValueAsString(emailNotification))
).andExpect(status().isOk());
}
}
However it fails with the following excetpion:
No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I looked at the Spring Boot tutorials on integration testing but cannot see what's wrong.
This is the controller under test:
#RestController
public class EmailController {
private static final Logger LOG = LoggerFactory.getLogger(EmailController.class.getName());
private final EmailService emailService;
#Autowired
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
#PostMapping(value = "/mail", consumes = MediaType.APPLICATION_JSON_VALUE)
public void send(#Valid #RequestBody EmailNotification emailNotification) {
try {
emailService.sendEmail(emailNotification);
} catch (MailException | MessagingException e) {
LOG.error("Error sending email: (recipient address: {}): {}", emailNotification.getEmailAddress(), e.getMessage());
}
}
}
Remove the #Profile and add #ActiveProfiles("test") from your test class they are not the same.
#Profile conditional evaluates the bean if the profile passed as argument is enabled, #ActiveProfiles change the profile to the profile passed as a argument, or in other words, activate it.

Adding configuration class to SpringBootTest breaks component scan

I am trying to disable real Mongo connection and replace it with Fongo mock in tests.
Here is my test class:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ControllerTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private MyService service;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void performTest() throws Exception {
... logic ...
}
}
It works fine unless I try to add my configuration file changing this line:
#SpringBootTest
to this:
#SpringBootTest(classes = TestConfig.class)
config class itself:
#Configuration
#ComponentScan
#EnableMongoRepositories
public class TestConfig extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "FongoDB";
}
#Override
public Mongo mongo() {
return new Fongo(getDatabaseName()).getMongo();
}
}
Then application fails to find beans and throws the next exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1486)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 28 more
How can I fix it and apply additional configuration properly?
try use
#SpringBootTest
#Import(value = TestConfig.class)
instead of
#SpringBootTest(classes = TestConfig.class)
keep #SpringBootTest and then create a class using #TestConfiguration with the beans in as follows:
#TestConfiguration
public class TransactionManagerTestConfiguration {
#Bean
public String getDatabaseName() {
return "FongoDB";
}
#Bean
public Mongo mongo() {
return new Fongo(getDatabaseName()).getMongo();
}
}
As per the javadoc: Configuration that can be used to define additional beans or customizations for a test. Unlike regular Configuration classes the use of TestConfiguration does not prevent auto-detection of SpringBootConfiguration.

Unsatisfied dependency during test

I have a spring boot 2.0.0 M2 application who run well.
I use autowired on constructor
#RequestMapping(value = "/rest")
#RestController
public class AddressRestController extends BaseController{
private final AddressService AddressService;
#Autowired
public AddressRestController(final AddressService AddressService) {
this.AddressService = AddressService;
}
...
}
#Service
public class AddressServiceImpl extends BaseService implements AddressService {
#Autowired
public AddressServiceImpl(final AddressRepository AddressRepository) {
this.AddressRepository = AddressRepository;
}
private final AddressRepository AddressRepository;
...
}
public interface AddressRepository extends JpaRepository<Address, Integer>, AddressRepositoryCustom {
}
#Repository
public class AddressRepositoryImpl extends SimpleJpaRepository implements AddressRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Autowired
public AddressRepositoryImpl(EntityManager em) {
super(Address.class, em);
}
...
}
When i try to run a basic test
#RunWith(SpringJUnit4ClassRunner.class)
public class AddressServiceTest {
#Autowired
private AddressService service;
#MockBean
private AddressRepository restTemplate;
#Test
public void getAddress(){
MockitoAnnotations.initMocks(this);
Pageable page = PageRequest.of(0, 20);
Page<Address> pageAdr = mock(Page.class);
given(this.restTemplate.findAll(page)).willReturn(pageAdr);
Page<AddressDto> pageDto = service.getAddress(page);
}
}
I get this error
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name
'com.sonos.arcor.service.AddressServiceTest': Unsatisfied dependency
expressed through field 'service'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type ''com.sonos.arcor.service.AddressService'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
I don't understand why i get this error.
You need to annotate the test with SpringBootTest so that spring initialize an application context
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications
#SpringBootTest
#RunWith(SpringJUnit4ClassRunner.class)
public class AddressServiceTest {
// the remaining test
}
Also you do not need MockitoAnnotations.initMocks(this);
Spring takes care of the mock handling
When [#MockBean is]used on a field, the instance of the created mock will also be
injected. Mock beans are automatically reset after each test method
see Mocking and spying beans

Spring MockBean annotation cause " No qualifying bean of type MockitoPostProcessor is defined"

I find if "old style", like RunWith annotation and SpringApplicationConfiguration annotation, mixed with "new style" MockBean annotation, it looks like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(TestApplication.class)
#FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class SomeUnitTest {
#InjectMocks
#Autowired
private IWantTestThisService iWantTestThisService;
#MockBean
private DependencyInAboveService dependencyInAboveService;
#Test
public void testSomething() {
// your test
}
}
It will case exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.test.mock.mockito.MockitoPostProcessor] is defined
To fix this exception, you should upgrade your code to
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class)
#FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class SomeUnitTest {
#Autowired
private IWantTestThisService iWantTestThisService;
#MockBean
private DependencyInAboveService dependencyInAboveService;
#Test
public void testSomething() {
// your test
}
}

Resources