3 ms·
This misses out on the main benefit of using annotations: The associated Java code is "just Java". Testing JAX-RS endpoints is just a matter of instantiating Ja
by stickfigure 3y ago
This misses out on the main benefit of using annotations: The associated Java code is "just Java". Testing JAX-RS endpoints is just a matter of instantiating Java objects and calling methods on them. Testing grumpyrest endpoints would require mocking the requestCycle other low-level http-oriented classes.
Here's the JAX-RS equivalent of the demo:
public class GreetingResource {
@POST
@Path("/make-greeting")
public MakeGreetingResponse greet(final MakeGreetingRequest request) {
if (request.addendum.isPresent()) {
return new MakeGreetingResponse("Hello, " + request.name + "! " + request.addendum.getValue());
} else {
return new MakeGreetingResponse("Hello, " + request.name + "!");
}
}
}
This is less noisy (no explicit parseBody) and much easier to test - you can just instantiate a GreetingResource and call the method, no mocking required.
- moring 3y agoThanks for your feedback. I'll take that into account. You are totally right about having to mock RequestCycle, which isn't good. Furthermore, RequestCycle isn't really mockable in its current state. I'm planning to solve both problems in a future version by making it mockable, providing standard mock implementations, and removing the dependency on other web objects (such as the servlet API) from code which is just using RequestCycle. The main point behind grumpyrest is that a lot is _not_ "just Java": While the handler method you presented is, the way it is mounted isn't, nor how dependencies are located and injected, nor how the JSON is validated and mapped to objects and back.
- chii 3y ago> the way it is mounted isn't, nor how dependencies are located and injected, nor how the JSON is validated and mapped to objects and back. the point of a framework is to hide some of that from you. The point of abstraction is that you _should not_ have to know how the server mounts the handlers, just that it does and it does it via a contract specified in the documentation.
- yardstick 3y agoShould things be abstracted one step further such that the REST endpoint class merely relays the right params to a Service class. So not much to test anyway (I’m not a 100% code coverage person, I test where it makes the most sense). This way your service can be accessed via REST as well as any other type of endpoint if you wanted (gRPC, SOAP, RMI, CORBA, etc). Defeats the point of this project but this approach has saved me a lot of work in the past when switching RPC stacks.
- stickfigure 3y ago> the way it is mounted isn't, nor how dependencies are located and injected, nor how the JSON is validated and mapped to objects and back. These are problems that every web application has to solve. You can use an existing framework or you can write your own. It might seem like there's a "hand-wire everything" option but in any mature system that turns into "write your own framework" as developers get tired of typing the same boilerplate over and over. For example, I can tell you right now that the team is going to get tired of typing `requestCycle.parseBody()` at the top of almost every method. Some clever sod will eventually figure out how to use AOP to make it implicit. Just like JAX-RS.
- iovrthoughtthis 3y agoTL;DR This is a bad example as we're mixing content construction with http service layer concepts. It should probably be more like: public class GreetingResource { @POST @Path("/make-greeting") public MakeGreetingResponse greet(final MakeGreetingRequest request) { return MakeGreetingResponse(Greeting(request.name, request.addendum)); } public Greeting(string name, Value addendum) { if (addendum.isPresent()) { return "Hello, " + request.name + "! " + addendum.getValue(); } else { return "Hello, " + request.name + "!"; } } } I think this is actually a little problematic. This handler is a likely location to mix http service logic with application logic and the interface exposed, via automatic parameter injection etc, implies the code in here should be application level but it's actually in the seam between then application and the http service layer. Testing custom http service logic should involve mocking as it depends on objects and behaviours owned by the http service layer. Code that we want to easily test for application level logic can be extracted into application level concepts.
- javcasas 3y agoSo how do you test that the method, the path and the body parser are correct? I have had bugs in all of these three.
- migf 3y agoHonestly I think your best bet for this is to have a predictable failure or degenerate input (empty body for post etc) and then write a test that hits all your endpoints in a docker container.
- javcasas 3y agoSo the plan is code a global try/catch, maybe write an e2e test and hope for the best? Sounds foolproof. I'll continue not doing decorators.
- stickfigure 3y agoThese are not really server-side test concerns. I know your first reaction is "WHAT?!" but hear me out. Let's say we add some server-side tests that verify that the method is POST and the path is /make-greeting. Have we now verified that the endpoint is correct? No - because the client was in fact posting to /create-greeting. We know the @POST and @Path annotations work; they are covered by the JAX-RS implementation tests. We can look at those annotations and know that the endpoint is a POST to /make-greeting. Testing that brings minimal value. The test we need is something that ensures the client and server both agree on the contract. By definition that can't be a server-side test, it's an e2e test. The good news is that it doesn't have to be a complicated e2e test; it just needs to ensure the method is called once. Maybe you're publishing a pure API? Add swagger, auto-generate the openapi spec, and diff against that. This works especially well if openapi is how you're publishing your documentation. > and the body parser If you're really concerned, it's pretty easy to write explicit tests for JSON serialization/deserialization. The good news is that you only need one test (or two for bidirectional) for each class. In both large and small companies I've never found this necessary.
- javcasas 3y ago