I am working with the Spark web framework and creating a RESTful API.
(http://sparkjava.com since there are multiple things out there named "Spark")
My employer's standards mandate that we write a series of unit tests that will be automatically run once a day to confirm that applications are still up.
Spark is easy to test myself using a tool like Postman but I have not found any good examples of JUnit tests being written with Spark or even with HTTP Requests being made programmatically with it.
Has anyone done this before? Is it possible?
we have develop a small library that facilitates the unit testing of Spark controllers/endpoints.
Github
Also, the version 1.1.3 is published in Maven Central Repository
<dependency>
<groupId>com.despegar</groupId>
<artifactId>spark-test</artifactId>
<version>1.1.3</version>
<scope>test</scope>
</dependency>
I had the same requirement that you and I found a way to make it work.
I searched over Spark source code and I found two classes that are useful:
SparkTestUtil: this class wraps Apache HttpClient and expose methods to make different http requests against a local web server (running in localhost) with customizable port (in constructor) and relative path (in requests methods)
ServletTest: it starts a Jetty instance in a local port with an application context and a relative directory path where a WEB-INF/web.xml file descriptor can be found. This web.xml will be use to simulate a web application. Then it uses SparkTestUtil to make http requests against this simulated application and assert results.
This is what I did: I created a junit test class that implements SparkApplication interface. In that interface I create and initialize the "controller" (a class of my application) in charge of answer http requests. In a method annotated with #BeforeClass I initialize the Jetty instance using a web.xml that refers to the junit test class as the SparkApplication and a SparkTestUtil
JUnit test class
package com.test
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.webapp.WebAppContext;
public class ControllerTest implements SparkApplication {
private static SparkTestUtil sparkTestUtil;
private static Server webServer;
#Override
public void init() {
new Controller(...)
}
#BeforeClass
public static void beforeClass() throws Exception {
sparkTestUtil = new SparkTestUtil(PORT);
webServer = new Server();
ServerConnector connector = new ServerConnector(webServer);
connector.setPort(PORT);
webServer.setConnectors(new Connector[] {connector});
WebAppContext bb = new WebAppContext();
bb.setServer(webServer);
bb.setContextPath("/");
bb.setWar("src/test/webapp/");
webServer.setHandler(bb);
webServer.start();
(...)
}
#AfterClass
public static void afterClass() throws Exception {
webServer.stop();
(...)
}
}
src/test/webapp/WEB-INF/web.xml file
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>SparkFilter</filter-name>
<filter-class>spark.servlet.SparkFilter</filter-class>
<init-param>
<param-name>applicationClass</param-name>
<param-value>com.test.ControllerTest</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SparkFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
This can be improved, but it is a good starting point I think.
Maybe some "spark-test" component could be created?
Hope this would be useful for you!
Here is my Solution.You just need additional add apache-http and junit dependency.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
public class SparkServer {
public static void main(String[] args) {
Spark.port(8888);
Spark.threadPool(1000, 1000,60000);
Spark.get("/ping", (req, res) -> "pong");
}
}
public class SparkTest {
#Before
public void setup() {
SparkServer.main(null);
}
#After
public void tearDown() throws Exception {
Thread.sleep(1000);
Spark.stop();
}
#Test
public void test() throws IOException {
CloseableHttpClient httpClient = HttpClients.custom()
.build();
HttpGet httpGet = new HttpGet("http://localhost:8888/ping");
CloseableHttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
BufferedReader rd = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
assertEquals(200, statusCode);
assertEquals("pong", result.toString());
}
}
Another approach wis to create a class which implements Route in each path or route. For example, if you have a route like next:
get("maintenance/task", (req, response) -> {....});
Then replace (req, response) -> {....} lambda by a class implementing Route.
For example:
public class YourRoute implements Route {
public Object handle(Request request, Response response) throws Exception {
....
}
}
Would be:
get("maintenance/task", new YourRoute());
Then you can unit testing YourRoute class using JUnit.
Related
I have
#MultipartConfig(location="/tmp", fileSizeThreshold=1048576,
maxFileSize=20848820, maxRequestSize=418018841)
#Path("/helloworld")
public class HelloWorld extends HttpServlet {
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
//#Consumes()
#Produces("text/plain")
public void doPost(#Context HttpServletRequest httpRequest) {
System.out.println("pinged");
//...
}
}
and I want to access the parts and get the files. But when I do httpRequest.getPart("token") I get java.lang.IllegalStateException: Request.getPart is called without multipart configuration. How do i get this to work? I am using Jersey and I know there is a better way to do this with FormDataMultiPart but my goal is to write a function that takes a HttpServletRequest and extracts some data and turns it into a custom object. (The use of a jersey server here is purely random. I want my function to work with other java servers that my not have FormDataMultiPart but do have HttpServletRequest).
First of all this is not how JAX-RS is supposed to be used. You don't mix the JAX-RS annotations with a Servlet. What you need to do is add the multipart config into the web.xml.
<servlet>
<servlet-name>com.example.AppConfig</servlet-name>
<load-on-startup>1</load-on-startup>
<multipart-config>
<max-file-size>10485760</max-file-size>
<max-request-size>20971520</max-request-size>
<file-size-threshold>5242880</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>com.example.AppConfig</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
Notice the servlet-name is the fully qualified class of the Application subclass.
And the rest I used to test
import javax.ws.rs.core.Application;
// if you're using `#ApplicationPath`, remove it
public class AppConfig extends Application {
}
#Path("upload")
public class FileUpload {
#POST
#Path("servlet")
public Response upload(#Context HttpServletRequest request)
throws IOException, ServletException {
Collection<Part> parts = request.getParts();
StringBuilder sb = new StringBuilder();
for (Part part: parts) {
sb.append(part.getName()).append("\n");
}
return Response.ok(sb.toString()).build();
}
}
I'm starting a Grizzly server as follows:
URI baseUri = UriBuilder.fromUri("http://localhost/").port(9998).build();
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.packages("com.example");
resourceConfig.property("contextConfig", applicationContext);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resourceConfig, false);
server.start();
In the package com.example, I have filters as follows:
#Component
#Provider
#Priority(0)
public class MyFilter implements ContainerRequestFilter {
#Context
private HttpServletRequest httpServletRequest;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
/* httpServletRequest is null here */
}
}
My filter is instantiated as expected by Spring. Also, JAX-RS detected it and uses the same instance instantiated by Spring. I'm trying to access the underlying HttpServletRequest but I can't find how.
I'm aware that the servlet request will never be injected until the filter instance is created as a proxy since the servlet request is request scoped. I tried to annotate the filter with #RequestScope, same thing.
You need to use the GrizzlyWebContainerFactory from
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>${jersey2.version}</version>
</dependency>
if you want to create servlet container. Currently, you are not creating a servlet container, that's why there is no HttpServletRequest available.
You can do something like
GrizzlyWebContainerFactory.create(baseUri, new ServletContainer(resourceConfig));
I have two classes Server (with the main method, starting the server) and StartPageServlet with a Servlet.
The most important part of the code is:
public class Server {
public static void main(String[] args) throws Exception {
// some code
// I want to pass "anObject" to every Servlet.
Object anObject = new Object();
Server server = new Server(4000);
ServletContextHandler context =
new ServletContextHandler(ServletContextHandler.SESSIONS);
context.addServlet(StartPageServlet.class, "/");
// more code
}
And the StartPageServlet:
public class StartPageServlet extends HttpServlet {
#Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// Here I want to access "anObject"
}
How do I do this?
Embedded Jetty is so wonderful here.
You have a few common options:
Direct instantiation of the servlet, use constructors or setters, then hand it off to Jetty via the ServletHolder (can be any value or object type)
Add it to the ServletContext in your main, and then access it via the ServletContext in your application (can be any value or object type).
Examples:
package jetty;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
public class ObjectPassingExample
{
public static void main(String args[]) throws Exception
{
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
// Option 1: Direct servlet instantiation and ServletHolder
HelloServlet hello = new HelloServlet("everyone");
ServletHolder helloHolder = new ServletHolder(hello);
context.addServlet(helloHolder, "/hello/*");
// Option 2: Using ServletContext attribute
context.setAttribute("my.greeting", "you");
context.addServlet(GreetingServlet.class, "/greetings/*");
server.setHandler(context);
server.start();
server.join();
}
public static class HelloServlet extends HttpServlet
{
private final String hello;
public HelloServlet(String greeting)
{
this.hello = greeting;
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
resp.getWriter().println("Hello " + this.hello);
}
}
public static class GreetingServlet extends HttpServlet
{
private String greeting;
#Override
public void init() throws ServletException
{
this.greeting = (String) getServletContext().getAttribute("my.greeting");
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
resp.getWriter().println("Greetings to " + this.greeting);
}
}
}
Singleton
You want to pass the same single instance to each servlet?
Use the Singleton pattern to create a single instance that is available globally.
The simplest fool-proof way to do that in Java is through an Enum. See Oracle Tutorial. Also see this article and the book Effective Java: Programming Language Guide, Second Edition (ISBN 978-0-321-35668-0, 2008) by Dr. Joshua Bloch.
So no need to pass an object. Each servlet can access the same single instance through the enum.
Per web app
If you want to do some work when your web app is first launching but before any servlet in that web app has handled any request, write a class that implements the ServletContextListener interface.
Mark your class with the #WebListener annotation to have your web container automatically instantiate and invoke.
I had a similar situation but needed to share a singleton with a servlet deployed via war with hot (re)deploy in a Jetty container. The accepted answer wasn't quite what I needed in my case since the servlet has a lifecycle and context managed by a deployer.
I ended up with a brute-force approach, adding the object to the server context, which persists for the life of the container, and then fetching the object from within the servlet(s). This required loading the class of the object in a parent (system) classloader so that the war webapp doesn't load its own version of the class into its own classloader, which would cause a cast exception as explained here.
Embedded Jetty server code:
Server server = new Server(8090);
// Add all classes related to the object(s) you want to share here.
WebAppContext.addSystemClasses(server, "my.package.MyFineClass", ...);
// Handler config
ContextHandlerCollection contexts = new ContextHandlerCollection();
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { contexts });
server.setHandler(handlers);
// Deployer config (hot deploy)
DeploymentManager deployer = new DeploymentManager();
DebugListener debug = new DebugListener(System.err,true,true,true);
server.addBean(debug);
deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
deployer.setContexts(contexts);
deployer.setContextAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");
WebAppProvider webapp_provider = new WebAppProvider();
webapp_provider.setMonitoredDirName("/.../webapps");
webapp_provider.setScanInterval(1);
webapp_provider.setExtractWars(true);
webapp_provider.setConfigurationManager(new PropertiesConfigurationManager());
deployer.addAppProvider(webapp_provider);
server.addBean(deployer);
// Other config...
// Tuck any objects/data you want into the root server object.
server.setAttribute("my.package.MyFineClass", myFineSingleton);
server.start();
server.join();
Example servlet:
public class MyFineServlet extends HttpServlet
{
MyFineClass myFineSingleton;
#Override
public void init() throws ServletException
{
// Sneak access to the root server object (non-portable).
// Not possible to cast this to `Server` because of classloader restrictions in Jetty.
Object server = request.getAttribute("org.eclipse.jetty.server.Server");
// Because we cannot cast to `Server`, use reflection to access the object we tucked away there.
try {
myFineSingleton = (MyFineClass) server.getClass().getMethod("getAttribute", String.class).invoke(server, "my.package.MyFineClass");
} catch (Exception ex) {
throw new ServletException("Unable to reflect MyFineClass instance via Jetty Server", ex);
}
}
#Override
protected void doGet( HttpServletRequest request,
HttpServletResponse response ) throws ServletException, IOException
{
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello from MyFineServlet</h1>");
response.getWriter().println("Here's: " + myFineSingleton.toString());
}
}
My build file for the servlet (sbt) placed the my.package.MyFineClass dependency into the "provided" scope so it wouldn't get packaged into the war as it will already be loaded into the Jetty server.
I would recommend that you investigate Google's solution to this problem... namely: dependency injection with Guice. They have a special servlet package that deals with servlets specifically.
We have unmanaged extension. We implemented custom communication API between server and client.
Now we need to ensure that client and server have same API version.
One solution - verify version in each resource. But this approach is messy and leads to code duplication.
What we want is to implement our own Filter and add it to Neo server.
Is this possible? If yes - then how?
This is possible!
Approach is a bit tricky and fragile, but it's working (blog post).
Dependency
You need neo4j-server dependency, because it contains SPIPluginLifecycle that is needed to get access to Neo4j web server.
So, add to your pom.xml:
<dependency>
<groupId>org.neo4j.app</groupId>
<artifactId>neo4j-server</artifactId>
<version>${version.neo4j}</version>
</dependency>
Filter
Create your filter. Let's take this one for example:
public class CustomFilter implements Filter {
public CustomFilter() {
}
#Override
public void init(final FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(final ServletRequest request,
final ServletResponse response,
final FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
#Override
public void destroy() {}
}
This filter doesn't do anything usefull - just continue chain further.
Lifecycle plugin
Now tricky part. We need to:
Implement SPIPluginLifecycle
Get web server
Add filter to web server
Code:
public final class ExtensionPluginLifecycle implements SPIPluginLifecycle {
private WebServer webServer;
private CustomFilter customFilter;
#Override
public Collection<Injectable<?>> start(final NeoServer neoServer) {
webServer = getWebServer(neoServer);
addFilters();
}
#Override
public void stop() {
removeFilters();
}
#Override
public Collection<Injectable<?>> start(final GraphDatabaseService graphDatabaseService,
final Configuration config) {
throw new IllegalAccessError();
}
private WebServer getWebServer(final NeoServer neoServer) {
if (neoServer instanceof AbstractNeoServer) {
return ((AbstractNeoServer) neoServer).getWebServer();
}
throw new IllegalArgumentException(String.format("Expected: [AbstractNeoServer], Received: [%s].", neoServer));
}
private void addFilters() {
customFilter = new CustomFilter();
webServer.addFilter(customFilter, "/extension-path/*");
}
private void removeFilters() {
webServer.removeFilter(customFilter, "/extension-path/*");
}
}
Tricky part is not so "legal" access to web server. This can break in future, so be carefull.
Note addFilters() and removeFilters() methods - this is why we have been done all this way.
Important: lifecycle plugin should be registered as service:
// file: META-INF/services/org.neo4j.server.plugins.PluginLifecycle
my.company.extension.ExtensionPluginLifecycle
So im trying to create a remote Rest (JSON) service inside an OSGi bundle based in Felix with Maven.
my basic service interface :
#Controller
#RequestMapping("/s/fileService")
public interface RestFileService {
#RequestMapping(value = "/file", method = RequestMethod.POST)
#ResponseBody
public String getFile(Long id);
}
My implementation of the interface
public class RestFileServiceImpl implements RestFileService{
public String getFile(Long id) {
return "test service";
}
}
Normally i would add this to my web.xml
<servlet>
<servlet-name>spring-mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc-dispatcher</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
And this would work fine inside a normal webapp.
But now i want to put this inside an OSGi bundle.
Servlet 3.0 allows you to use #WebServlet to declare a servlet without the web.xml
So i created a RestServlet
#WebServlet(value="/rest", name="rest-servlet")
public class RestServlet implements ServletContextListener {
private static Log sLog = LogFactory.getLog(RestServlet.class);
public void contextInitialized(ServletContextEvent arg0) {
sLog.info("initializing the Rest Servlet");
}
public void contextDestroyed(ServletContextEvent arg0) {
sLog.info("un-initializing the Rest Servlet");
}
}
This is my OSGi activator:
public class Activator implements BundleActivator {
private static Log sLog = LogFactory.getLog(Activator.class);
public void start(BundleContext context) throws Exception {
/*
* Exposing the Servlet
*/
Dictionary properties = new Hashtable();
context.registerService(RestFileService.class.getName(), new RestFileServiceImpl(), properties );
sLog.info("Registered Remote Rest Service");
}
public void stop(BundleContext context) throws Exception {
sLog.info("Unregistered Remote Rest Service");
}
}
I know Felix has its own http implementation with JAX but im trying to do this with spring annotations and as little XML as possible.
Can i force it to register the annotation driven 3.0 servlet ?
What am i doing wrong ? is this even possible ?
If you're looking for an easy way to do REST in OSGi, take a look at some of the web components provided by the Amdatu project. This page pretty much explains how to create a REST service: https://amdatu.org/application/web/ and there is also a video which will talk you through the whole process: https://amdatu.org/generaltop/videolessons/