Play route/Controller just for unit test - java

I have a method that takes a Play Http.Context and "does some stuff" with the session. I want to write a unit test for just that method. Specifically, I want to test that if a request comes in with certain headers my method works correctly. It seems like the easiest way to do that reliably is to create a FakeApplication and a Controller for my test. Then I'd use Helpers.fakeRequest to get a request and Helpers.route to route that request to my controller. The controller would call my method, set some variables, etc. and I could then assert success and such.
Seems like a splendid plan but I can't figure out how to add a route to my controller in the FakeApplication. Note that this controller isn't really part of my app - it's just something I want to use for this one test. So I want to define it and construct in just this one unit test; I don't want to add it to my conf/routes file.
Specifically, I want something like this:
// Maybe I can use GlobalSettings.onRouteRequest but the return type
// is play.api.mvc.Handler which seems inaccessible from Java
FakeApplication app = Helpers.fakeApplication(new MyGlobalSettings());
Http.Request request = Helpers.fakeRequest().withCookies(...).withBody(...);
Controller testContoller = new MyTestController();
// This doesn't exist, but I want something like this
app.addRoute("/foo", ctx -> testController.method(ctx));
running(app, () -> {
Helpers.route("/foo");
assertThat(testContoller.itWorked()).isTrue();
}
I'm running Play 2.2.3 and writing in Java, not Scala.
I do realize I can construct an Http.Context directly and pass that to my method. However, this isn't my preferred approach for a few reasons:
The Http.Context constructor takes the plain text of the session variables. I want to test that things work correctly when the request contains the encrypted session cookie.
The Http.Context constructor is poorly documented and seems a bit off. For example, you can pass an Http.Request to the constructor, but you also pass the cookie data and session data. So what happens to the cookie/session data on the request? Does it get merged with the other data passed? Ignored?
The Http.Context constructor is difficult to use from Java as it requires a play.api.mvc.RequestHeader, which can't be constructed in Java, and a play.mvc.Http.Request which can't be "usefully" constructed from Java (you can construct one, but without cookies, headers, etc. and FakeRequest can't be converted to an Http.Request).
It feels more "black box" to send in a request and ensure things work rather than try to figure out how this particular version of Play converts my request it an Http.Context (e.g manually constructing a context seems more likely to break with new versions of play).
Any ideas?

Play Tests in format
running(fakeApplication(), () -> {
...
});
Are good for testing a running play app without the HTTP layer. However in your case you're dependent on having a http context so I your options are to either add in the http layer...
running(testServer(3333), fakeApplication(), () -> {
WSResponse wsResponse = WS.url("http://localhost:3333/foo").setHeader("fizz", "buzz").get().get(30, TimeUnit.SECONDS);
....
//assert some stuff
});
or maybe try using PowerMockito and mock out the HTTP.Context call. As you point out this is more brittle but will allow to pragmatically spin up a quick unit test. Something like
#RunWith(PowerMockRunner.class)
public class FooTest {
#PrepareForTest({ Http.Context.class })
#Test
public void test() {
mockStatic(Http.Context.class)
mockStatic(Http.class)
Http.Context mockContext = mock(Http.Context.class);
Map<String, String> args new HashMap<>();
args.put("a","b");
mockContext.args = args;
PowerMockito.when(Http.Context.current()).thenReturn(mockContext);
ClassUnderTest cut = new ClassUnderTest();
cut.someMethod();
//assertions
}
}

#Before
public void startPlay() {
String conf = System.getProperty("config.file");
if (conf == null) {
System.setProperty("config.file", "../../conf/test.conf");
}
System.setProperty("play.http.router", "customer.Routes");
super.startPlay();
}

This is how you add an additional test route without changing your conf/routes file. But MyTestController.java needs to be located at project/app instead of project/test.
// app/controllers/TestController.java
public static Result foo() {
return ok();
}
// conf/test.routes
GET /foo controllers.TestController.foo
// test/controllers/TestTestController.java
#Before
Configuration config = new Configuration(ConfigFactory.parseFile(new File("conf/test.conf")).resolve());
Map<String, Object> configMap = config.asMap();
Map<String, Object> application = (Map<String, Object>) config.get("application");
application.put("router", "test.Routes");
configMap.put("application", application);
fakeApplication(configMap);
#Test
FakeRequest fakeRequest = Helpers.fakeRequest("GET", "/foo");
Result result = Helpers.route(fakeRequest);
assertThat(Helpers.status(result)).isEqualTo(200);

Related

Why need to then Mono.empty() in the default implementation of generated Open API Spring code?

Here is the default implementation of an API generated by the openapi-generator-maven-plugin using Spring Boot as library:
default Mono<ResponseEntity<Void>> testAPI(
#Parameter(hidden = true) final ServerWebExchange exchange
) {
Mono<Void> result = Mono.empty();
exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED);
return result.then(Mono.empty());
}
Being new to this, there are several things I don't understand:
There are two Mono.empty(), one being the result, one inside the then(Mono.empty()), why is it done like that?
Why can't it just returns one? e.g. return Mono.empty();
Or better yet, remove also the pass in exchange and just do:
return Mono.just(ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build());
The default implementation is more like a template that gives you a hint how to complete this API controller. For an API controller usually you need to create a response in at least two steps: first fetch data from some source and then make it a valid response. The template code can give you a start point to write such code. For example, I can write the following code using the template:
public class UsersApiController implements UsersApi {
#Override
public Mono<ResponseEntity<String>> usersGet(
#Parameter(hidden = true) final ServerWebExchange exchange
) {
var client = WebClient.create("http://calapi.inadiutorium.cz/");
Mono<String> result = client.get().uri("/api/v0/en/calendars/general-en/today").retrieve().bodyToMono(String.class);
return result.map(rep -> ResponseEntity.status(HttpStatus.OK).body(rep));
}
}
The first Mono.empty becomes the WebClient that gets data from another API, and the second Mono.empty is replaced by a map operation that transforms the API result to ResponseEntity object. If the generator only generates a Mono.empty, newcomers may feel difficult to start writing the controller.

Mock an object which is not injected

I'm new to Java programming and I have the following code:
import javax.ws.rs.client.ClientBuilder;
public class Foo {
private final Client http;
Foo() {
http = ClientBuilder.newClient().register(CurlRequestFactory.getCurlRequestFactory().get(LOGGER, “someString”));
}
public someMethod() {
Invocation.Builder request = http.target(getURI(“someUri”)).request().header(“someHeader”, “someValue”);
Response response = request.get();
}
}
I want to write a unit test for someMethod() where request.get() would throw an exception. For this I require that request object should be set as a mock object.
But I'm unable to do so as it is being initialized directly instead of getting injected.
I know I can mock an object if it was getting injected as below:
Response responseMock = Mockito.mock(Response.class);
Mockito.when(responseMock.get()).thenThrow(new Exception("someMessage"));
But I couldn't find anything which works for my scenario.
PS: I don't want to use Powermock.
I have come across few things that Mokito does not support. One is this scenario. Either you have to change your code to inject it or use PowerMockito. I don't think there is any other option.
Your method is probably doing too many things. I imagine that you don't want to just return the Response from someMethod. Refactor your code to someMethod(Response response) or even better someMethod(Pojo responseBody) and then test that.
I don't know what your use case is, but someMethod probably belongs to another class (I'll call it Bar) separate from the http client class (Foo). You can then inject Foo into Bar and do the test. Is there any reason why you wouldn't want to refactor to that?

Can a query parameter name be variable? Like: #QueryParam("//anything")

I have a resource class and within it a #GET that takes one query param called operation (this should be static) and then I want to take a variable number of other query params that can be named anything.
My first thought was to do something like this:
public Response get(
#QueryParam("operation") String operation,
#QueryParam("list") final List<String> list) {
//do stuff
}
The problem here is that I would have to make a request like:
...?operation=logging&list=ABC&list=XYZ
While what I want is to be able to have something like this:
...?operation=logging&anything=ABC&something_else=XYZ
Is there a way to make the list query param #QueryParam(//anything)?
In doing some information gathering I ran across this sort of approach:
#GET
public void gettest(#Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
String operation = queryParams.getFirst("operation");
for (String theKey : queryParams.keySet()) {
System.out.println(queryParams.getFirst(theKey));
//do stuff with each other query param
}
}
Is multivaluedmap the way to go for this situation -- Or is there a way to use a variable query param name? Or a better approach? Thanks.
Edit/Update:
This is using javax.ws.rs
The use case is: this application being used as a tool for mocking responses (used for testing purposes in other applications). The mock responses are retrieved from a DB by looking up the 'operation' and then some sort of 'id'. The actual id used could be any of the "list" query params given. The reason for this is to give flexibility in different applications to use this mock service -- the urls in applications may be constructed many different ways and this makes if so one doesn't have to change around their code to be able to use the mock service.
As in this question, use a map:
#RequestMapping(value = {"/search/", "/search"}, method = RequestMethod.GET)
public String search(
#RequestParam Map<String,String> allRequestParams, ModelMap model) {
return "viewName";
}

Play! Framework - SaaS subdomain name filter

I know this question has been asked quite a few times, however, I have a different approach of what I want to achieve.
Since Play 1.1, you're able to match hosts. This is very useful, however, it means that for every controller, I will need to pass through the subdomain route param. This is quite a burden and repeatful if I have hundreds of controllers which use the subdomain param.
Is there not a way to create a filter which looks at the host name before everything else is executed and then sets an on-the-fly config value for that request?
For example (brainstorming), a filter would do the following:
// use request host, but hard-coded for now...
String host = "test.example.com";
Pattern p = Pattern.compile("^([a-z0-9]+)\\.example\\.com$");
Matcher m = p.matcher(host);
if (m.matches()) {
// OUT: test
System.out.println(m.group(1));
System.setProperty("host", m.group(1));
}
And in the models I'd do something like System.getProperty("host");
I know this isn't how it should be done, but I'm just brainstorming.
At least with this way:
I don't have to pass the subdomain param through to every
controller.
I don't have to pass the subdomain param through to any models
either
Models have direct access to the subdomain value so I can filter out objects that belong to the client
Also, I'm aware that System.setProperty() always applies to the entire JVM; which is a problem. I only want this value to be available throughout the duration of the request. What should I use?
Let's analyse. How would you do it? What would be a good approach? Is this possible with Play? I'm sure there are quite a few running into this problem. Your input is highly appreciated.
I think you are close. If I had to do this, I would write a controller annotated with #Before and have that method extract the hostname from the request headers and put it in renderArgs.
Something like this (I haven't tested it):
public class HostExtractor extends Controller {
#Before
public static void extractHost() {
// Code to read from request headers and extract whatever you need here.
String host = 'Your Code Here'
renderArgs.put("hostname", host);
}
}
Then, in your other controllers, you tell it you want to use that controller above as a filter.
#With(HostExtractor.class)
public class MyController extends Controller {
public static void homepage() {
String hostname = renderArgs.get("host", String.class);
// Do whatever logic you need to render the page here.
}
}
Again, I haven't tested this, but I'm doing something similar to cache objects in memcache. I hope that helps!

mock https request in java

Let's say I'm writing an application and I need to be able to do something like this:
String url = "https://someurl/";
GetMethod method = new GetMethod(URLEncoder.encode(url));
String content = method.getResponseBodyAsString();
Is there a way to provide a mock server that would let me handle the https request? What I'm looking for is a way to write unit tests, but I need to be able to mock the part that actually goes out to https://someurl so I can get a known response back.
Take a look at jadler (http://jadler.net), an http stubbing/mocking library I've been working on for some time. The 1.0.0 stable version has been just released, it should provide the capabilities you requested:
#Test
public void getAccount() {
onRequest()
.havingMethodEqualTo("GET")
.havingURIEqualTo("/accounts/1")
.havingBody(isEmptyOrNullString())
.havingHeaderEqualTo("Accept", "application/json")
.respond()
.withTimeout(2, SECONDS)
.withStatus(200)
.withBody("{\"account\":{\"id\" : 1}}")
.withEncoding(Charset.forName("UTF-8"))
.withContentType("application/json; charset=UTF-8");
final AccountService service = new AccountServiceRestImpl("http", "localhost", port());
final Account account = service.getAccount(1);
assertThat(account, is(notNullValue()));
assertThat(account.getId(), is(1));
}
#Test
public void deleteAccount() {
onRequest()
.havingMethodEqualTo("DELETE")
.havingPathEqualTo("/accounts/1")
.respond()
.withStatus(204);
final AccountService service = new AccountServiceRestImpl("http", "localhost", port());
service.deleteAccount(1);
verifyThatRequest()
.havingMethodEqualTo("DELETE")
.havingPathEqualTo("/accounts/1")
.receivedOnce();
}
You essentially have two options:
1. Abstract the call to the framework and test this.
E.g. refactor the code to allow you to inject a mock implementation at some point. There are many ways to do this. e.g. create a getUrlAsString() and mock that. (also suggested above). Or create a url getter factory that returns a GetMethod object. The factory then can be mocked.
2. Start up a app server as part of the test and then run your method against it. (This will be more of an integration test)
This can be achieved in an number of ways. This can be external to the test e.g. the maven jetty plugin. or the test can programmatically start up the server. see: http://docs.codehaus.org/display/JETTY/Embedding+Jetty
Running it over https will complicate this but it will still be possible with self signed certs. But I'd ask yourself - what exactly you want to test? I doubt you actually need to test https functionality, its a proven technology.
Personally I'd go for option 1 - you are attempting to test functionality of an external library. That is usually unnecessary. Also it's good practice to abstract out your dependencies to external libraries.
Hope this helps.
If you are writing a unit test, you dont want any external dependencies. from the api,
GetMethod
extends
HttpMethod
so you can easily mock it with your favorite mocking library. Your
method.getResponseBodyAsString()
call can be mocked to return any data you want.
You can wrap that code in some class and have WebClient.getUrl() and then mock (e.g. jmock) that method to return stored files - say
expectation {
oneOf("https://someurl/"), will(returnValue(someHTML));
}
Take a look at JWebUnit http://jwebunit.sourceforge.net/
Here is an example of a test...Its really quite intuitive.
public class ExampleWebTestCase extends WebTestCase {
public void setUp() {
super.setUp();
setBaseUrl("http://localhost:8080/test");
}
public void test1() {
beginAt("/home");
clickLink("login");
assertTitleEquals("Login");
setTextField("username", "test");
setTextField("password", "test123");
submit();
assertTitleEquals("Welcome, test!");
}
}
You could always launch a thttpd server as part of your unit test to serve the requests locally. Though, ideally, you have a well tested GetMethod, and then you can just mock it, and not have to actually have a remote server around for ALL of your tests.
Resources
thttpd: http://www.acme.com/software/thttpd/
To what extend are you interested in mocking this "Get" call, because if you are looking for a general purpose mocking framework for Java which integrates well with JUnit and allows to setup expectations which are automatically asserted when incorporated into a JUnit suite, then you really ought to take a look at jMock.
Now without more code, it's hard to determine whether this is actually what you are looking for, but a (somewhat useless) example, of something similar to the example code you wrote, would go something like this:
class GetMethodTest {
#Rule public JUnitRuleMockery context = new JunitRuleMockery();
#Test
public void testGetMethod() throws Exception {
// Setup mocked object with expectations
final GetMethod method = context.mock(GetMethod.class);
context.checking(new Expectations() {{
oneOf (method).getResponseBodyAsString();
will(returnValue("Response text goes here"));
}});
// Now do the checking against mocked object
String content = method.getResponseBodyAsString();
}
}
Use xml mimic stub server, that can simulate static http response based on request parameters, headers, etc. It is very simple to configure and use it.
http://xmlmimic.sourceforge.net/
http://sourceforge.net/projects/xmlmimic/

Categories