Spring Boot - Mock Interceptors - java

I'm having an issue while testing Rest request.
However test is being intercepted by one of my interceptors.
In short I want to mock interceptors behaviour rather than calling actual implementation.
For simplicity purposes to visualise the issue here is a simply Get request
#Controller
public class HiController {
#ResponseBody
#GetMapping(value = "/hi", produces = {"application/json"})
public String hi() {
return "hi";
}
}
with simple interceptor and config file
#Component
public class LoggingInterceptor implements HandlerInterceptor {
#Override
public final boolean preHandle(HttpServletRequest request, final HttpServletResponse response, final Object handler) {
System.out.println("Logging token interceptor");
return true;
}
}
#Component
public class InterceptorAppConfig implements WebMvcConfigurer {
#Autowired
private LoggingInterceptor loggingTokenInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingTokenInterceptor);
}
}
the purpose of test was to call rest endpoint and to mock interceptor
#RunWith(SpringRunner.class)
#WebMvcTest(HiController.class)
public class HiControllerTest extends MvcTest {
#Autowired
private MockMvc mockMvc;
#MockBean
RestTemplate restTemplate;
#Mock
private HiController controller;
private static final String AUTHENTICATE_USER = "/hi";
#Test
public void mandatoryFieldMissing() throws Exception {
this.mockMvc.perform(get(AUTHENTICATE_USER))
.andExpect(content().string(containsString("hi")))
.andExpect(status().isOk());
}
}
#RunWith(SpringRunner.class)
public abstract class MvcTest {
#MockBean
LoggingInterceptor loggingTokenInterceptor;
}
When I run this test ^ LoggingInterceptor is still being called.
How can I configure tests to have mock interceptors?

You are using #MockBean. From the documentation,
If a bean, compatible with the declared class exists in the context, it replaces it by the mock. If it is not the case, it adds the mock in the context as a bean.
The mocked bean does not have behavior. Realize that by mocking the bean, the onus is on you to define the behavior.
You can either disable mocking the bean(to allow for default behavior)
Mockito.doCallRealMethod().when(loggingTokenInterceptor).preHandle(Mockito.any(), Mockito.any(), Mockito.any());
Or you can define the behavior for the mocked bean yourself.

Related

Why tested class based autowire annotation throw null exception?

I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
#Service
#RequiredArgsConstructor
#Slf4j
public class BuilderServiceImpl implements BuilderService{
#Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
#Override
public BuilderDto getByEmail(String email){
}
#Override
public List<BuilderMinDto> getAll() {}
#Override
public List<BuilderMinDto> getAll(int page, int size) {}
#Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
#SpringBootTest
#RequiredArgsConstructor
#Slf4j
class BuilderServiceImplTest {
#Mock
private BuilderRepository builderRepository;
#Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
#AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
#Test
void getByEmail(){}
#Test
#Disabled
void getAll() { }
#Test
#Disabled
void testGetAll() {}
#Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("test#builders.com");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
#Component
#Slf4j
#RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use #Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
In order to make #Autowire work you have to use the instance of BuilderServiceImpl (object under test) created by spring itself.
When you create the object like this (by yourself, manually):
#BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used #Mock for BuilderRepository and AdminUserRepository.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use #MockBean instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
#SpringBootTest
class MyTest
{
#MockBean
BuilderRepository builderRepo;
#MockBean
AdminUserRepository adminUserRepo;
#Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
#Test
void mytest() {
...
}
}
Add #Autowire annotation Annotations on below fields. Error due your not initialized below object In BuilderServiceImpl
#Autowire
private final BuilderRepository builderRepository;
#Autowire
private final AdminUserRepository adminUserRepository;
Why are you creating BuildService manually? If you do this, set AutoMapper manualy too.
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
underTest.setAutoMapper(new AutoMapper());
}
You are not using di.

How to avoid using an interceptor in Spring boot integration tests

I have an issue while testing REST requests. On my application I have an interceptor that checks for token validity before allowing the requests. However for my integration tests I would like to bypass the check. In other words I'd like either to shunt the interceptor or to mock it to always return true.
Here is my simplified code:
#Component
public class RequestInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("Authorization");
if (token != null) {
return true;
} else {
return false;
}
}
}
#Configuration
public class RequestInterceptorAppConfig implements WebMvcConfigurer {
#Autowired
RequestInterceptor requestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestInterceptor).addPathPatterns("/**");
}
}
and the tests:
#SpringBootTest(classes = AppjhipsterApp.class)
#AutoConfigureMockMvc
#WithMockUser
public class DocumentResourceIT {
#Autowired
private DocumentRepository documentRepository;
#Autowired
private MockMvc restDocumentMockMvc;
private Document document;
public static Document createEntity() {
Document document = new Document()
.nom(DEFAULT_NOM)
.emplacement(DEFAULT_EMPLACEMENT)
.typeDocument(DEFAULT_TYPE_DOCUMENT);
return document;
}
#BeforeEach
public void initTest() {
document = createEntity();
}
#Test
#Transactional
public void createDocument() throws Exception {
int databaseSizeBeforeCreate = documentRepository.findAll().size();
// Create the Document
restDocumentMockMvc.perform(post("/api/documents")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(document)))
.andExpect(status().isCreated());
}
}
When running the tests it always go through the interceptor and gets rejected since I have no valid token. My code here is simplified, I can not get a valid token for testing and so I really need to skip the interceptor.
Thanks for your help
To mock it (in an integration test):
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
// non-static imports
#SpringBootTest
// other stuff
class IntegrationTest {
#MockBean
RequestInterceptor interceptor;
// other stuff
#BeforeEach
void initTest() {
when(interceptor.preHandle(any(), any(), any())).thenReturn(true);
// other stuff
}
// tests
}
What #BeforeEach and #SpringBootTest do, you know; Mockito's any() just says "regardless of argument"; for #MockBean and Mockito's when-then, the Javadoc is good enough that I feel no need to add information.
I would solve this by using a profile on the interceptor. In your test you dont run with the profile (bean is not injected). In your production or which ever env you need it you run with the new profile.
of course you need to change the usage a bit. This should work:
#Configuration
public class RequestInterceptorAppConfig implements WebMvcConfigurer {
#Autowired
Collection<RequestInterceptor> requestInterceptors;
#Override
public void addInterceptors(InterceptorRegistry registry) {
requestInterceptors.forEach(interceptor -> registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}

JUnit | MockServer | RestTemplate

I have one service called PostService, which has instance variable called connectionManager Autowired.
Inside this connectionManager there is a one instance variable called restTemplate Autowired.
And in the configuration java file, object of RestTemplate is created with some logic.
When I write a test case around it, and create a MockRestServiceServer with an expected URL and post method, and in return expect a response with some body. and when I execute a test case I don't get mocked response from this mockedRestServiceServer.
As much, I can sense, this is because during test execution, a real object of rest template is created and mock server is not used to send mocked response.
can someone help me, how to overcome from this ?
class PostServie {
#Autowired
private ConnectionManager connectionManager;
public void postMessage(String msg) {
// some logic
}
}
#Component
class ConnectionManager {
#Autowired
private RestTemplate restTemplate;
public String getToken(){
ResponseEntity<String> response = this.restTemplate.postForEntity(url, request, String.class);
//returns response.body() in string format
}
}
#Configuration
class Configuration {
#Bean
public RestTemplate getRestTemplate(){
// some logic and returns object of RestTemplate
}
}
#RunWith(SpringRunner.class)
class PostServiceTest {
#Autowired
private PostMessageService postMessageService;
#Resource(name="authServerRestTemplet")
private RestTemplate authServerRestTemplet;
private MockRestServiceServer mockedAuthServerRestTemplet;
private final String requestToAuthServer ="grant_type=client_credentials&client_id=ceapiClientId";
#Before
public void setUp() {
mockedAuthServerRestTemplet =
MockRestServiceServer.createServer(authServerRestTemplet);
}
#Test
public void postServeiceSuccess () {
mockedAuthServerRestTemplet.expect(requestTo(ACCESS_TOKEN_URI)).andExpect(content().string(requestToAuthServer)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess("{abc : 'abc'}", MediaType.APPLICATION_JSON));
postMessageService.postMessage(jsonMessage);
}
}
}
You're creating an instance of RestTemplate but not using it. What is MockRestServiceServer doing?
I can also see a wrong utilisation of #Resource and #Autowired, which you shouldn't specify in your test. Try use #MockBean.
Autowiring works so that the resource you create in your test is injected in the real class, not the other way round.
I'll just add some sample code here, which might help you achieve your goal.
#RunWith(SpringRunner.class)
#WebMvcTest
public abstract class AbstractControllerTest {
#Autowired
protected MockMvc mockMvc;
#MockBean
protected Service service;
#Before
public void setUp() {
Mockito.reset(service);
}
}
and then your test
public class YourTest extends AbstractControllerTest {
#Test
public void shouldDoSomething() throws Exception {
// given
List<someStuff> stuff = new ArrayList<>();
stuff.add(new Whatever("content", "John Smith");
// when - service is in the abstract controller!
when(service.getSomething()).thenReturn(stuff);
// then
mockMvc.perform(get("/posts/1/whatever").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content", is("content")))
.andExpect(jsonPath("$[0].author", is("John Smith"))));
}

Does spring #GetMapping work with MockMvc

HelloController.java
#RestController
class HelloController {
#GetMapping(value = "{id}/hello")
public ModelAndView listAPI(#PathVariable("id") String profileId) {
ModelAndView mav = new ModelAndView();
return mav;
}
}
HelloControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = HelloConfigTest.class)
class HelloControllerTest {
#Inject
private WebApplicationContext webApplicationContext;
#Inject
private Foo mockFoo
#InjectMocks
HelloController helloController;
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testHello() throws Exception {
mockMvc.perform(
get("/{id}/campaigns", "id1"))
.andExpect(status().isOk()));
}
}
// I have another test that directly calls the controller method.
// So I need #InjectMocks to get an instance of the controller
#Test
public void test2() {
when(mockFoo.getX()).thenReturn(true);
helloController.saveAPI();
}
HelloConfigTest.java
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
#Bean
public mockFoo() {
return Mockito.mock(Foo.class);
}
}
The response that I get here is 404 and I expect 200.
But it works and I get 200 if I change #GetMapping to #RequestMapping(value="{id}/hello", method=RequestMethod.GET)
Am I missing anything here ?
Your configuration is extremely bare bones
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
It doesn't register any Spring MVC infrastructure beans, either implicitly or explicitly.
When MockMvc, internally, creates a TestDispatcherServlet to test your #Controller class, it has to defer to some default Spring MVC infrastructure types.
Among these infrastructure types is HandlerMapping which is
to be implemented by objects that define a mapping between requests and handler objects.
The default implementation used by the TestDispatcherSerlet is DefaultAnnotationHandlerMapping (an old class) which looks for #RequestMapping specifically, it doesn't recursively do a meta-annotation lookup. Your #GetMapping annotated method is therefore not found and not registered as a handler.
If, instead, you configure your application context with #EnableWebMvc
#Configuration
#ComponentScan("com.test.controller")
#EnableWebMvc
class HelloConfigTest {
Spring will implicitly register a RequestMappingHandlerMapping, which does do this "recursive" lookup for the annotation hierarchy (called merging). Since #GetMapping is annotated with #RequestMapping, the annotated handler method will be found and registered.
As for the #InjectMocks, note that the instance referenced by the field is different from the one used to handle the request performed by the MockMvc object. The former is managed by Mockito, the latter by Spring.

Mocking Spring bean's method behavior breaks aspects

I searched SO and found bunch of other questions that looked similar but not exactly, so I'll ask another one.
I have Spring application and say I created custom aspect (looking for CatchMe annotation) to log exceptions in a specific way. I want to test the aspect by mocking the behavior of one of my Spring #Service class's method so it throws exception when it is called. Then in another method, annotated with my custom annotation #CatchMe, I call the first method. What I expect to happen is the exception to get logged. Unfortunatelly the exception is thrown but the aspect is not triggered. So how can I make the aspect to get triggered in this test using Mockito?
Note: I've checked those (plus a bunch more):
Unit testing Spring #Around AOP methods
Spring Aspect not triggered in unit test
Spring: cannot inject a mock into class annotated with the #Aspect annotation
but most of them are Controller related and not Service related and I want to test only the service.
The Test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {BeanConfig.class})
public class MyServiceTest {
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(service, "serviceDep", serviceDep);
}
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Services
#Service
public class MyService {
#Autowired
private MyServiceDependency serviceDep;
#CatchMe
public void execute() {
serviceDep.process();
}
}
#Service
public class MyServiceDependency {
public Object process() {
// may throw exception here
}
}
Configuration and Aspect
#Configuration
#EnableAspectJAutoProxy
#ComponentScan(basePackages = {"com.example.services"})
public class BeanConfig { .. }
#Aspect
#Component
public class CatchMeAspect {
#Around("#annotation(CatchMe)")
public Object catchMe(final ProceedingJoinPoint pjp) throws Throwable {
try {
pjp.proceed();
} catch (Throwable t) {
// fency log
}
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CatchMe {}
EDIT: The functionality works but I want to verify it with the test.
Actually it is working as expected, however you are running in a side effect of proxy based AOP, especially class based proxies in this case.
Currently you are setting the field on the proxy and not on the actual object inside the proxy. Which is what you actually want. To obtain the actual instance use AopTestUtils.getUltimateTargetObject and then use that in the ReflectionTestUtils.setField method.
#Autowired
#InjectMocks
private MyService service;
#Mock
private MyServiceDependency serviceDep;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
MyService serviceToInject = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setField(serviceToInject, "serviceDep", serviceDep);
}
However I think that approach is wrong, when you start messing around like this there is a better way. Simply use Spring to inject the mock. Create a specific #Configuration class for this test case. Make it a internal public static class and for the dependency add a mocked #Bean.
#Configuration
#Import(BeanConfig.class)
public static class TestBeanConfig {
#Bean
public MyServiceDependency myServiceDependency() {
return Mockito.mock(MyServiceDependency.class);
}
}
Now in your test class you can simply #Autowire both beans and not need to use reflection or whatever to set dependencies.
#RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyServiceDependency serviceDep;
#Test
public void test() {
when(serviceDep.process()).thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
throw new Exception("Sample message.");
}
});
service.execute();
}
}
Which will take care of the correct dependencies.
I had the same problem as #nyxz and this is intentional, see https://github.com/spring-projects/spring-boot/issues/7243.
Inspired by #M. Deinum following solution worked for me with Spring Boot 2.3.4.RELEASE and JUnit 5.
We will just provide a mocked bean without #MockedBean
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MyServiceTest {
#Autowired
private MyService service;
#Test
public void test() {
service.execute();
}
static class TestBeanConfig {
#Bean
#Primary
public MyServiceDependency myServiceDependency() {
MyServiceDependency myServiceDependency = Mockito.mock(MyServiceDependency.class)
// Add behavior of mocked bean here
return myServiceDependency;
}
}
}

Categories