How Do I Unit Test A Jersey REST API Without Running A Server? - spring-boot

I am working with a REST API that is using Jersey with Spring Boot (so no specific application context in XML or Java) and Spring Data JPA.
I want to write unit tests on the GET and POST endpoints, however, I don't want to start a web server as it takes too long.
If I use JerseyTest my Spring Beans don't get loaded into the context.
public class InMemoryContainerPackageTest extends
JerseyTestNg.ContainerPerClassTest {
#Override
protected TestContainerFactory getTestContainerFactory() {
return new InMemoryTestContainerFactory();
}
#Override
public Application configure() {
ResourceConfig config = new ResourceConfig()
.register(SpringLifecycleListener.class)
.register(RequestContextFilter.class)
.register(this)
.register(MyController.class)
.packages("com.my.service");
return config;
}
If I use SpringBootTest it starts up a web server which takes about 30 seconds and ideally I want all my tests to complete in under 5 seconds otherwise developers won't run them.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestNGClass1 extends AbstractTestNGSpringContextTests {
I don't think MockMvc works with Jersey endpoints.

If I use JerseyTest my Spring Beans don't get loaded into the context.
What you can do is set the property "contextConfig" in your ResourceConfig. The value will be a Spring ApplicationContext instance. So if you are using Java configuration, you would just use an AnnotationConfigApplicationContext.
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.property("contextConfig",
new AnnotationConfigApplicationContext(SpringConfig.class));
}
Here, SpringConfig is an arbitrary Spring #Configuration class. Below is a complete example.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
public class SpringTest extends JerseyTest {
public static class MessageService {
public String getMessage() {
return "Hello World";
}
}
#Configuration
public static class SpringConfig {
#Bean
public MessageService service() {
return new MessageService();
}
}
#Path("test")
public static class TestResource {
#Autowired
private MessageService service;
#GET
public String get() {
return service.getMessage();
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.property("contextConfig",
new AnnotationConfigApplicationContext(SpringConfig.class));
}
#Override
public TestContainerFactory getTestContainerFactory() {
return new InMemoryTestContainerFactory();
}
#Test
public void testIt() {
Response res = target("test")
.request()
.get();
String msg = res.readEntity(String.class);
System.out.println(msg);
assertThat(msg).isEqualTo("Hello World");
}
}
As far as the JPA, you are going to have to configure that yourself. When using Spring Boot, all of the JPA bootstrapping is taken care of. If you are going to use Jersey Test Framework, then you are ignoring all Spring Boot configuration.
It's really not that hard to configure JPA yourself. It basically consists of configuring a DataSource, a TransactionManager, a JpaVendorAdaptor, and a LocalContainerEntityManagerFactoryBean. And to enable the Spring Data repositories, you just need to use the #EnableJpaRepositories.
Have a look at this complete example configuration.
Another thing to be wary of is that when we use the Jersey Test Framework, we will not have the test scoped transactions (and rollbacks) that you will get when working with Spring Test. So when you write your tests, you need to take this into consideration.

Related

Pointcut expression not matching the Spring Data method despite specfying exact name in expression

In my Spring Boot project, I have AddressRepository that brings all addresses from Database. I have an Aspect class and a pointcut expression that executes after the findAll() method called. When I execute my test case, the Advice is not being triggered and other methods like findAll(Sort sort), findAll(Pageable pageable) work just fine. I am not sure if this is a bug with Spring Boot or my expression. I tried with Spring Boot 2.0.5 and 2.1.0, nothing seemed to solve my problem
AddressLogging.java
#Aspect
#Configuration
public class AddressLogging {
private Logger log=LoggerFactory.getLogger(AddressLogging.class);
//#Pointcut("execution(* com.springtesting.repo.AddressRepository.*(..))")
#Pointcut("execution(* com.springtesting.repo.AddressRepository.findAll())")
public void getAddresses() {}
#After("getAddresses()")
public void afterAdvice() {
log.error("Log Message: Inside afterAdvice() advice");
}
}
AopTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
public class AopTest {
#Autowired
private AddressRepository addressRepository;
#Test
public void getAddresses() {
//addressRepository.findAll(PageRequest.of(0,20, Sort.by("id")));
addressRepository.findAll();
}
#Test
public void findAddressById() {
addressRepository.findById(1L);
}
}
AddressRepository
public interface AddressRepository extends JpaRepository<Address,Long> {}
A Spring AOP aspect should also be a #Component and be picked up by component scan. I have no idea why you added #Configuration to the aspect instead because there is no configuration here.
Maybe your test of a separate configuration class should bear the #Configuration annotation and you should also activate something like #EnableAspectJAutoProxy(proxyTargetClass = true) and #ComponentScan(basePackages = { "de.scrum_master" }).
Here is a snippet from one of my Spring AOP playground projects (I hardly use it, I don't use Spring AOP or even Spring itself, usually I use the more powerful AspectJ:
package de.scrum_master.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(basePackages = { "de.scrum_master" })
public class Application2 {
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new AnnotationConfigApplicationContext(Application2.class);
B b = (B) appContext.getBean("b");
System.out.println(b.getData("bbb"));
A a = (A) appContext.getBean("b");
System.out.println(a.getData("aaa"));
}
}

Spring Cloud Contracts and Spring Security issues

I am using Spring Cloud Contracts in projects to test microservices, everything is ok. But when I added Spring Security in the producer side, the GET return 401 status code instead of 200.
#Autowired
WebApplicationContext context;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(this.context);
}
My question is:
I have to avoid Security settings in the contract tests?
If I want to consider the security configuration, how to make it work.
I successfully used a custom annotation on the base class, as documented here test-method-withsecuritycontext
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUserDetails {
String username() default "email#example.com";
String role() default "DEFAULT_ROLE";
String password() default "123456";
}
and then
#WithMockCustomUserDetails
class AccountBase {
...
}
Two options AFAIK.
A) Use authorization header
request {
method 'POST'
urlPath '/check'
headers {
contentType(applicationJsonUtf8())
header(authorization(), "Bearer eyJhb.... ")
}
}
B)
Add #WithMockUser in my base test
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.WebApplicationContext;
#SpringBootTest
#WithMockUser //this will ensure a mock user will be injected to all requests
public abstract class BaseTestCloudContract {
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}

Autowire working in unit test but not in main java class

I've a domain class that I want to auto-populate from external config. Here is my domain class:
#Data
#Configuration
#PropertySource("classpath:application.properties")
public class StudioVo {
#Value("${studio.code}")
private code;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my context xml:
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
<property name="studioVo" ref="StudioVo" />
</bean>
<bean id="StudioConfigVo" class="com.sdm.studio.domain.StudioVo" />
</bean>
Here is the class where I want to use the vo:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
private StudioVo studioVo;
public List<Studio> read() throws Exception {
System.out.println("getCode: " + studioVo.getCode()); //code is null here
return null;
}
}
However when I run it via unit test by autowiring, it runs fine. Like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class StudioTest {
#Autowired
private StudioVo studioVo;
#Test
public void testAutoPopulationOfStudio(){
System.out.println("getCode: "+ studioVo.getCode()); // works!
// Assert.assertTrue(studioVo.getCode().equals("102"));
}
}
Not sure what's going on here - I'm working with an old Spring Batch application wrapped in Spring Boot (so there is a mix of XML based and Java based config - and may be that is the cause of this issue). What am I missing?
In your StudioTest, you are autowiring StudioReader where as you missed the #Autowired in your StudioReader code, so add it as shown below:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
#Autowired //add this so that studioVo can be injected
private StudioVo studioVo;
//add other code
}
Please be certain to note that using #Autowire requires a chain of Spring-managed beans below it from wherever you are using it including the class in which you are using #Autowire. That is because Spring needs the precedent references to match up the object-reference hierarchy. I.e., in business logic layer ClassA, you want to #Autowire a field. ClassA itself needs to be a managed bean. Further, if the field you want to #Autowire holds an object that has referential dependencies to other objects (and most do), these also must be Spring-managed.
For example, the following will work:
package com.example.demo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MessageRunner {
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage = (SetterMessage) (new AnnotationConfigApplicationContext(DemoConfiguration.class)).getBean("setterMessage");
setterMessage.setMessage("Finally it works.");
p(setterMessage.getMessage());
}
private static void p(String s) {
System.out.println(s);
}
}
DemoConfiguration.java looks like this:
package com.example.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.example.demo")
public class DemoConfiguration {
}
SetterMessage.java, this:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage {
private String message = null;
#Autowired
private SetterMessage2 setterMessage2;
public String getMessage(){
return message+setterMessage2.getSubMessage();
}
public void setMessage(String message) {
this.message = message;
setterMessage2.setSubMessage("("+message+")");
}
}
SetterMessage2.java:
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage2 {
private String subMsg = "";
public void setSubMessage(String msg) {
subMsg = msg;
}
public String getSubMessage() {
return subMsg;
}
}
Note that SetterMessage2.java is annotated as a Component (#Service) but no field in it is autowired. That is because it's the end of the object reference chain. But because it is a Component, it can be autowired into SetterMessage.java. However look at MessageRunner.java's main() method and field declarations. Note that the class field SetterMessage is NOT autowired. If it were annotated as #Autowired, main() would fail at runtime, throwing an NPE with the reference to setterMessage in main(). This is because MessageRunner.java is not marked as some kind of component. So we need to grab a valid instance of MessageSetter from the application context and use it.
To emphasize, the following version of MessageRunner.java's main() method WILL FAIL, throwing an NPE, if MessageRunner.java looked like this:
...
public class MessageRunner {
#Autowired // <-- This will not do the job for us
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage.setMessage("Finally it works."); // NPE here on ref to setterMessage
p(setterMessage.getMessage());
}
...
This is a real gotchya for people new to Spring. In fact, I'd place it among the Top Five Spring Newbie Discouragers and a really evil, pernicious detail that has caused new Spring programmers countless hours in aggravation and Google searches. So I do hope that noting this phenom here will save at least some newbies time and high blood pressure spikes.
Note: If you go to create the above classes in your IDE, bear in mind these were developed with Spring Boot enabled.

Should Mockito be used with MockMvc's webAppContextSetup in Spring 4?

I'm having difficulties getting Mockito and MockMvc working together when I use the webAppContextSetup together. I'm curious if it's because I'm mixing the two in a way they were never intended.
Source: https://github.com/zgardner/spring-boot-intro/blob/master/src/test/java/com/zgardner/springBootIntro/controller/PersonControllerTest.java
Here is the test I'm running:
package com.zgardner.springBootIntro.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static java.lang.Math.toIntExact;
import static org.hamcrest.Matchers.is;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import com.zgardner.springBootIntro.Application;
import com.zgardner.springBootIntro.service.PersonService;
import com.zgardner.springBootIntro.model.PersonModel;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class PersonControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private DefaultListableBeanFactory beanFactory;
#Mock
private PersonService personService;
#InjectMocks
private PersonController personController;
#Before
public void setup() {
initMocks(this);
// beanFactory.destroySingleton("personController");
// beanFactory.registerSingleton("personController", personController);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getPersonById() throws Exception {
Long id = 999L;
String name = "Person name";
when(personService.findById(id)).thenReturn(new PersonModel(id, name));
mockMvc.perform(get("/person/getPersonById/" + id))
.andDo(print())
.andExpect(jsonPath("$.id", is(toIntExact(id))))
.andExpect(jsonPath("$.name", is(name)));
}
}
I was expecting that when mockMvc performed the mock of that HTTP call, it would use the PersonController I defined in my test. But when I debug through, it's using the PersonController which was created by the SpringJunit4ClassRunner on the test boot up.
I found two ways to get this to work:
Inject the bean factory, remove the old personController singleton, and add my own. This is ugly, and I am not a fan.
Wire everything up using the standaloneSetup instead of webAppContextSetup. I may do this instead as I don't have to touch the bean factory.
Here are some different articles I've found that somewhat touch on the topic:
Spring Tutorial - Building REST Services This just autowires in the repos to clear out the data before the integration test takes place.
Use Spring MVC Test framework and Mockito to test controllers This uses Mockito along with webAppContextSetup, but this is in Spring 3. (I'm using Spring Boot)
Unable to mock Service class in Spring MVC Controller tests This uses the standaloneSetup, which does work in my case too.
Thoughts?
You might be interested in the new testing features coming in Spring Boot 1.4 (specifically the new #MockBean annotation). This sample shows how a service can be mocked and used with a controller test.
For some reason the Mockito annotations #Mock et #InjectMocks won't work in this case.
Here's how I managed to make it work :
Instantiate the personService bean manually using your own Test context
make Mockito create a mock for this personService.
let Spring inject these mocks in the controller PersonController.
You should have your TestConfig :
#Configuration
public class ControllerTestConfig {
#Bean
PersonService personService() {
return mock(PersonService.class);
}
}
In your PersonControllerTest, you won't need the personController anymore, since it's managed by the mockMvc through the perform method. You also don't need to execute initMocks() because you initialize your mocks manually inside the Spring config. You should have something like :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, ControllerTestConfig.class})
#WebAppConfiguration
public class PersonControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
PersonService personService;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getPersonById() throws Exception {
Long id = 999L;
String name = "Person name";
when(personService.findById(id)).thenReturn(new PersonModel(id, name));
mockMvc.perform(get("/person/getPersonById/" + id))
.andDo(print())
.andExpect(jsonPath("$.id", is(toIntExact(id))))
.andExpect(jsonPath("$.name", is(name)));
}
}
I sometimes use Mockito to fake Spring beans with usage of #Primary and #Profile annotations. I wrote a blog post about this technique. It also contains link to fully working example hosted on GitHub.
To extend florent's solution, I encountered performance issues and extensibility issues creating separate configurations for every controller test which needed a different set of service mocks. So instead, I was able to mock out my application's service layer by implementing a BeanPostProcessor alongside my tests which replaces all #Service classes with mocks:
#Component
#Profile("mockService")
public class AbcServiceMocker implements BeanPostProcessor {
private static final String ABC_PACKAGE = "com.mycompany.abc";
#Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (StringUtils.startsWith(bean.getClass().getPackage().getName(), ABC_PACKAGE)) {
if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, bean.getClass())
|| AnnotationUtils.isAnnotationInherited(Service.class, bean.getClass())) {
return mock(bean.getClass());
}
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
return bean;
}
}
I enabled these mocks in specific tests with an #ActiveProfiles annotation:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:/WEB-INF/application-context.xml"})
#ActiveProfiles("mockService")
public class AbcControllerTest {
private MockMvc mvc;
#Before
public final void testBaseSetup() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
Lastly, the injected Mockito mocks were wrapped in an AopProxy causing Mockito's expect and verify calls to fail. So I wrote a utility method to unwrap them:
#SuppressWarnings("unchecked")
protected <T> T mockBean(Class<T> requiredType) {
T s = context.getBean(requiredType);
if (AopUtils.isAopProxy(s) && s instanceof Advised) {
TargetSource targetSource = ((Advised) s).getTargetSource();
try {
return (T) targetSource.getTarget();
} catch (Exception e) {
throw new RuntimeException("Error resolving target", e);
}
}
Mockito.reset(s);
return s;
}

Add a Servlet Filter in a Spring Boot application

I'd like to have ETag suport. For this purpose there is a ShallowEtagHeaderFilter which does all the work. How can I add it without declaring it in my web.xml (which actually does not exist, because I somehow got by without it so far)?
P.S. I use Spring Boot 1.1.4
P.P.S. Here's a full solution
package cuenation.api;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.DispatcherType;
import java.util.EnumSet;
#Configuration
public class WebConfig {
#Bean
public FilterRegistrationBean shallowEtagHeaderFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ShallowEtagHeaderFilter());
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
registration.addUrlPatterns("/cue-categories");
return registration;
}
}
When using Spring Boot
As mentioned in the reference documentation, the only step needed is to declare that filter as a Bean in a configuration class, that's it!
#Configuration
public class WebConfig {
#Bean
public Filter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
}
When using Spring MVC
You're probably already extending a WebApplicationInitializer. If not, then you should convert your webapp configuration from a web.xml file to a WebApplicationInitializer class.
If your context configuration lives in XML file(s), you can create a class that extends AbstractDispatcherServletInitializer - if using configuration classes, AbstractAnnotationConfigDispatcherServletInitializer is the proper choice.
In any case, you can then add Filter registration:
#Override
protected Filter[] getServletFilters() {
return new Filter[] {
new ShallowEtagHeaderFilter();
};
}
Full examples of code-based Servlet container initialization are available in the Spring reference documentation.
A bit late answer.
My solution was to create custom annotation:
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
// ...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Component
public #interface Filter {
#AliasFor(annotation = Component.class, attribute = "value")
String value() default "";
}
And then simply apply it to the filter implementations:
#Filter
public class CustomFilter extends AbstractRequestLoggingFilter {
#Override
protected void beforeRequest(HttpServletRequest request, String message) {
logger.debug("before req params:", request.getParameterMap());
}
#Override
protected void afterRequest(HttpServletRequest request, String message) {
logger.debug("after req params:", request.getParameterMap());
}
}
See more: #AliasFor, Spring custom annotations question

Resources