I'm trying to implement an OGC API - Features service using Spring Boot. The specification includes links to self, parent collections and such, so I figured Spring HATEOAS could help with that. Unfortunately, I'm having trouble with the bbox-crs parameter.
An example demonstration:
#RestController
public class DemoController {
#GetMapping(path = "/link", produces = "application/geo+json")
public Link getLink(#RequestParam(required = false, name = "bbox-crs") String bboxCrs) {
return linkTo(methodOn(DemoController.class).getLink(bboxCrs))
.withSelfRel()
.expand()
.withType("application/geo+json");
}
}
Test:
#SpringBootTest
#AutoConfigureMockMvc
class DemoApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void getLinkWithParam() throws Exception {
String expectedJson = new ObjectMapper().writeValueAsString(
new Link("http://localhost/link?bbox-crs=hello").withType("application/geo+json"));
mockMvc.perform(get("/link?bbox-crs=hello"))
.andExpect(content().contentType("application/geo+json"))
.andExpect(content().json(expectedJson));
}
#Test
public void getLinkNoParam() throws Exception {
String expectedJson = new ObjectMapper().writeValueAsString(
new Link("http://localhost/link").withType("application/geo+json"));
mockMvc.perform(get("/link"))
.andExpect(content().contentType("application/geo+json"))
.andExpect(content().json(expectedJson));
}
}
The first test succeeds, but the second test fails with the error Illegal character in path at index 23: http://localhost/link{?bbox-crs}.
According to issue #799, this is working as intended, as the URI Template spec does not allow for hyphens in variable names. I'm trying to build an URI, though, not an URI Template. Is there some way to
achieve what I'm after? Maybe by configuring Spring or by creating the link in a different way?
Related
Okay, so I am pretty new to testing and Spring Boot in general, so please correct me if I am doing something completely wrong here in the first place.
As a project my team and I are making a Web Application using Spring Boot where we are making calls to the Microsoft Graph API in some of our services. See this service for cancelling an event in a user's calendar:
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.extensions.IGraphServiceClient;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import org.springframework.stereotype.Service;
#Service
public class CancelEventService {
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
IGraphServiceClient graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
This is working great, but I have been struggling for a couple of days with how to write unit tests for this. The way I see it, I want to either make mocks of the GraphServiceClient, or use a tool like WireMock to make the request go to a mockserver and return some values I configure.
I've tried doing both, but I can't make mocks of GraphServiceClient because it is not a Bean in my project, so I can't figure out how I should proceed to make an implementation I can autowire in to my Service.
When it comes to WireMock I am not even sure I understand if it is capable of doing what I want to, and if it is, I sure haven't been able to configure it correctly (note that we are using JUnit 5 for this project). A simple example where you make a GET-request to Google.com and return "Hello" via WireMock would suffice to get me going here.
Any concrete examples of what I should do, or even just a nod in the right direction would be well appreciated.
Well, I cannot assure you that it will work but it will give you a better landscape of the situation:
1) So first, we need to make a slight change on your service.
Need to extract IGraphServiceClient from the method so we can mock it later, see:
#Service
public class CancelEventService {
private IGraphServiceClient graphServiceClient;
#Autowired
public CancelEventService(IGraphServiceClient graphServiceClient){
this.graphServiceClient = graphServiceClient;
}
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
2) The test would look like this:
(Notice that all we are using here is included in spring boot test module, so you shouldn't need to add anything to the project dependencies)
#RunWith(SpringRunner.class)
public class CancelEventServiceTest {
private IGraphServiceClient graphServiceClientMock;
private CancelEventService serviceToBeTested;
#Before
public void setUp(){
graphServiceClientMock = Mockito.mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
serviceToBeTested = new CancelEventService(graphServiceClientMock);
}
#Test
public void test_1() {
serviceToBeTested.cancelEvent("token", "id");
verify(graphServiceClientMock, times(1)).me().events("id").buildRequest()
.delete();
}
}
Hope it helps!
As a follow up on this, we found a solution to the problem by creating a class
GraphServiceClientImpl with a method that returns an instantiated GraphServiceClient.
#Component
public class GraphServiceClientImpl {
public IGraphServiceClient instantiateGraphServiceClient(final String token) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
return GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
}
}
#Service
public class CancelEventService{
private GraphServiceClientImpl graphServiceClientImpl;
public CancelEventService(final GraphServiceClientImpl graphServiceClientImpl) {
this.graphServiceClientImpl = graphServiceClientImpl;
}
public void cancelEvent(final String token, final String id) {
IGraphServiceClient graphServiceClient = graphServiceClientImpl.instantiateGraphServiceClient(token);
graphServiceClient
.me()
.events(id)
.buildRequest()
.delete();
}
}
Then, our test:
#ExtendWith(MockitoExtension.class)
class CancelEventServiceTest {
#Mock
private GraphServiceClientImpl graphServiceClientImpl;
#InjectMocks
private CancelEventService cancelEventService;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
void cancelEvent_Successful() {
//given
IGraphServiceClient serviceClientMock = mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
given(graphServiceClientImpl.instantiateGraphServiceClient(anyString())).willReturn(serviceClientMock);
//when
cancelEventService.cancelBooking("Token", "1");
//then
verify(serviceClientMock, times(1)).me();
}
}
Probably not the optimal solution, but it works. Any other takes on this would be welcome!
I know you can set the server.contextPath in application.properties to change the root context.
Also, I can add an additional context in the application config for Spring Boot like the following example (in Groovy) to add an "/api" to the URL mappings of the root context:
#Bean
ServletRegistrationBean dispatcherServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
reg.name = "dispatcherServlet"
reg.addInitParameter("contextConfigLocation", "")
reg.addUrlMappings("/api/*")
reg.loadOnStartup = 2
reg
}
}
I am trying to have a separate base URI "/api" specifically for web service calls, that I can leverage for security, etc. However using the above approach will mean that any of my URIs, web service or not, can be reached with "/" or "/api", and provides no concrete segregation.
Is anyone aware of a better approach to set a base path for all #RestController(s) using configuration, without having to formally prefix every controller with /api/? If I am forced to manually prefix the URI for each controller, it would be possible to mistakenly omit that and bypass my security measures specific to web services.
Here is a reference in Stack Overflow to the same type of question, which was never completely answered:
Spring Boot: Configure a url prefix for RestControllers
In continuation to the currently accepted solution the github issue addresses the same.
Spring 5.1 and above you can implement WebMvcConfigurer and override configurePathMatch method like below
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Now all the #RestControllers will have /api as the prefix path alongside the path configured.
Official Documentation
There's a new solution to solve this kind of problem available since Spring Boot 1.4.0.RC1 (Details see https://github.com/spring-projects/spring-boot/issues/5004)
The solution of Shahin ASkari disables parts of the Auto configuration, so might cause other problems.
The following solution takes his idea and integrates it properly into spring boot. For my case I wanted all RestControllers with the base path api, but still serve static content with the root path (f.e. angular webapp)
Edit: I summed it up in a blog post with a slightly improved version see https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/
#Configuration
public class WebConfig {
#Bean
public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
private final static String API_BASE_PATH = "api";
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
Class<?> beanType = method.getDeclaringClass();
RestController restApiController = beanType.getAnnotation(RestController.class);
if (restApiController != null) {
PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
.combine(mapping.getPatternsCondition());
mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
}
super.registerHandlerMethod(handler, method, mapping);
}
};
}
};
}
}
Also You can achieve the same result by configuring WebMVC like this:
#Configuration
public class PluginConfig implements WebMvcConfigurer {
public static final String PREFIX = "/myprefix";
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}
}
Implement WebMvcConfigurer on any #Configuration class.
Override configurePathMatch method.
You can do many useful things with PathMatchConfigurer e.g. add prefix for several classes, that satisfy predicate conditions.
I had the same concern and was not a fan of the Spring EL option due to the issues documented and I wanted the prefix to be tightly controlled in the controllers but I did not want to depend on the developers doing the right thing.
There might be a better way these days but this is what I did. Can you guys see any downsides, I am still in the process of testing any side-effects.
Define a custom annotation.
This allows a developer to explicitly provide typed attributes such as int apiVersion(), String resourceName(). These values would be the basis of the prefix later.
Annotated rest controllers with this new annotation
Implemented a custom RequestMappingHandlerMapping
In the RequestMappingHandlerMapping, I could read the attribute of the custom annotation and modify the final RequestMappingInfo as I needed. Here are a few code snippets:
#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyCustomRequestMappingHandlerMapping();
}
}
And in the MyCustomRequestMappingHandlerMapping, overwrite the registerHandlerMethod:
private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
public MyCustomRequestMappingHandlerMapping() {
super();
}
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
// find the class declaring this method
Class<?> beanType = method.getDeclaringClass();
// check for the My rest controller annotation
MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
if (myRestAnnotation != null) {
// this is a My annotated rest service, lets modify the URL mapping
PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
// create a pattern such as /api/v${apiVersion}/${resourceName}
String urlPattern = String.format("/api/v%d/%s",
myRestAnnotation.apiVersion(),
myRestAnnotation.resourceName());
// create a new condition
PatternsRequestCondition apiPattern =
new PatternsRequestCondition(urlPattern);
// ask our condition to be the core, but import all settinsg from the old
// pattern
PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}",
beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
mapping = new RequestMappingInfo(
mapping.getName(),
updatedFinalPattern,
mapping.getMethodsCondition(),
mapping.getParamsCondition(),
mapping.getHeadersCondition(),
mapping.getConsumesCondition(),
mapping.getProducesCondition(),
mapping.getCustomCondition()
);
}
super.registerHandlerMethod(handler, method, mapping);
}
}
Slightly less verbose solution which doesn't duplicate the logic of checking the annotation, but only changes the mapping path:
private static final String API_PREFIX = "api";
#Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
return new WebMvcRegistrationsAdapter() {
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
if (mappingForMethod != null) {
return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
} else {
return null;
}
}
};
}
};
}
Side effects
Your error controller will also be mapped under /api/error, which breaks error handling (DispatcherServlet will still redirect errors to /error without prefix!).
Possible solution is to skip /error path when adding /api prefix in the code above (one more "if").
Someone has filed an issue in the Spring MVC Jira and come up with a nice solution, which I am now using. The idea is to use the Spring Expression Language in the prefix placed in each RestController file and to refer to a single property in the Spring Boot application.properties file.
Here is the link of the issue: https://jira.spring.io/browse/SPR-13882
I have developed an rest service using spring-boot-starter-Hateoas, and I am able to get the json output properly as shown below:
"_embedded": {
"bills":
{
uid: "123"
code: "0000"
And I need to write unit-test case for the same using mockito. The code I have written is as below.
ApplicationTest.java:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class ApplicationTest {
BillControllerAutoTest:
public class BillControllerAutoTest {
private BillService mockBillService;
private MockMvc mockMvc;
private static final String BILL_UID = "99991";
#Before
public void setupController() {
mockBillService= mock(BillService .class);
BillController controller = new BillController (mockBillService);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testGetBills() throws Exception {
// some fake data
final List<Bill> fakeBillList= new ArrayList<>();
fakeBillList.add(BillFake.bill("1234"));
when(mockBillService.getBills(BILL_UID))
.thenReturn(fakeBillList.stream());
// execute and verify
mockMvc.perform(get("/bills/" + BILL_UID ))
.andExpect(content().string(containsString("\"embedded\":{\"bills\"")))
BillController.java:
#RestController
#RequestMapping(value = "/bills/{billUid}", produces = "application/hal+json")
public class BillController extends BaseController {
private BillService billService;
#RequestMapping(method = RequestMethod.GET, value = "")
public ResponseEntity<Resources<Resource<Bill>>> getBills(#PathVariable String billUid) {
return resourceListResponseEntity(
() -> billService.getBills(billUid),
bill-> createResource(billUid),
resources -> resources.add(linkTo(methodOn(BillController .class)
.getBills(billUid)).withSelfRel()));
}
Dependencies:
dependencies {
compile "org.springframework.boot:spring-boot-starter-hateoas"
compile "org.springframework.boot:spring-boot-starter-ws"
compile "org.springframework.boot:spring-boot-starter-actuator"
testCompile("org.springframework.boot:spring-boot-starter-test")
}
My build is failing with the following stackTrace:
java.lang.AssertionError: Response content
Expected: a string containing "\"_embedded\":{\"bills\""
but: was
"content":[
{
uid: "123"
code: "0000"
This means "_embedded : { bills" is not available in the response returned by mockMvc of the unit test. Am I missing any configuration, kindly let me know. Any help would be greatly appreciated.
I've answered very similar question here: Spring's MockMVC Responses Don't Match Browser Response
In a nutshell: spring HATEOAS adds additional configuration for rendering hal properly (as described here: http://docs.spring.io/spring-hateoas/docs/0.19.0.RELEASE/reference/html/#configuration). In your tests you have to apply this configuration manually. Check the first link for details on how to do it
I am currently writing tests for some REST-full Services I wrote. The services I am testing are written in Java and use MongoDb/Morphia. The tests call on the services, some of which in turn write to a test collection. I need to clean up after the tests and delete the data I injected. What is the best way to go about this?
Here is an example of one of my simple services:
package org.haib.myerslab.services;
#Path("/database")
public class DatabaseService {
#Inject
private Datastore ds;
#Path("/genre/")
#POST
#Produces("application/json")
public GenreDTO postFromGenreDTO(#Context UriInfo uri, GenreDTO form) throws ParseException {
Genre myNewGenre = DtoToDomainMapper.gerneFromGenreDTO(form);
myNewGenre.setId(form.getId());
ds.save(myNewGenre);
return new GenreDTO(myNewGenre);
}
}
And here is an example of my Arquillian test:
#RunWith(Arquillian.class)
public class GeneTest {
private static String myId = "myGenreId";
private static String myGenre = "myGenre";
private static String myGenreInfo = "myGenreInfo";
#Deployment
public static WebArchive getDeployment() {
return TestHelper.getDeployment();
}
#Test
#RunAsClient
#InSequence(1)
public void canPostGenre(#ArquillianResource URL baseURL) throws Exception {
GenreDTO newGenre = new GenreDTO();
newGenre.setGenre(myGenre);
newGenre.setGenreInfo(myGenreInfo);
newGenre.setId(myId);
String url = baseURL.toURI().resolve("/database/genre/").toString();
JsonNode rootNode = TestHelper.postUrl(url, newGene);
assertEquals(myGenre, rootNode.get("genre").asText());
assertEquals(myGenreInfo, rootNode.get("genreInfo").asText());
assertEquals(myId, rootNode.get("id").asText());
}
}
Where the getDeployment function looks like this:
public static WebArchive getDeployment() {
File[] depend = Maven.resolver().loadPomFromFile("pom.xml").importRuntimeDependencies().resolve().withTransitivity().asFile();
WebArchive war = ShrinkWrap.create(WebArchive.class).addClass(TestHelper.class)
.addClass(Genre.class).addClass(Application.class).addPackage("org/haib/myerslab")
.addPackage("org/haib/myerslab/database").addPackage("org/haib/myerslab/genre")
.addPackage("org/haib/myerslab/dto").addPackage("org/haib/myerslab/dto/genre")
.addAsLibraries(depend).addAsWebInfResource("jboss-deployment-structure.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml").setWebXML("test-web.xml");
return war;
}
So where I am lost is, what is the best way to Inject the database in an #After, and clear our the Genre Class I posted into it so that my next test doesn't have it there.
How should I do this? Is there another way?
Take a look at nosql-unit. It provides annotations and rules that help you with seeding datasets, comparing expectations and cleaning up MongoDB.
To get your MongoDB into a pristine state before executing a test, you can simply use the following Annotation with the `CLEAN_INSERT´ :
#UsingDataSet(locations="my_data_set.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)
public void canPostGenre() { ...}
If you need the behavior around the integration testing lifecycle with MongoDB to be more powerful, you can also roll your own based on the ideas of nosql-unit. Also make sure to check out Junit Rules.
Both 'aop:aspectj-autoproxy' and 'mvc:annotation-driven' are present in the XML config.
Both of these classes are defined as a bean inside of the same XML.
Using Spring 3.2.3.RELEASE and Google App Engine 1.8.1 in a local/dev environment.
My pointcut does not execute.
My advice. Declared inside a class annotated with #Aspect.
#Component
#Aspect
public class RequestLimiter {
private MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
#Pointcut("within(#pcs.annotations.LimitRequests com.zdware.pcs.controllers.PingCollectorController)")
public void methodRequestLimited(){}
#Around("methodRequestLimited() && args(req,limitRequests)")
public Object requestGateWay(ProceedingJoinPoint jp, HttpServletRequest req,LimitRequests limitRequests) throws Throwable {
// do stuff
}
}
The method I am using to test in the controller layer.
#Controller
public class PingCollectorController {
#RequestMapping(value="/test")
#LimitRequests(requestTimeLimit = 1, functionName = "Test")
public String test(){
return "test"; // this will return me to a jsp that doesnt exist, but my advice is still not executing.
}
}
Is CGLIB in the classpath? It will be needed to generate the proxy (since your controller does not implement an interface, spring cannot use a simpler JDK proxy).