Skip to content
This repository has been archived by the owner on Sep 18, 2019. It is now read-only.

Best practice for hapi route testing #214

Closed
mattiloh opened this issue Jan 8, 2016 · 11 comments
Closed

Best practice for hapi route testing #214

mattiloh opened this issue Jan 8, 2016 · 11 comments
Assignees
Labels

Comments

@mattiloh
Copy link

mattiloh commented Jan 8, 2016

I recently switched from express to hapi to develop a REST API and so far it feels like a really good decision. Especially the testing with lab and server.inject is a pleasure.

Before, with express, I wrapped the whole application with chai-http and tested all api endpoints with actual http-requests. With hapi I'm doing it similarly: I require the server-file and after the composition of the whole server with all its routes and plugins, I do the tests with server.inject, which is much more comfortable than doing "real" http-requests. So with every test, authentication logic is tested as well as possible collisions with other routes, which seems to be a good idea.

But now I realized, that server.inject can even bypass authentication-logic by simply setting the credentials option. So I'm very tempted to compose a highly reduced server for every endpoint-test-suite, that only contains the route(-plugin), that I want to test. But I'm too much a hapi-beginner to foresee, if this will lead to reliable test results.

What's your opinion on this? I would love to see some best practice on api-route testing.

@devinivy
Copy link

devinivy commented Jan 8, 2016

Are your routes inheriting some default authentication strategy from their connection configuration? I can't tell exactly what your reduced server will be missing, nor what the intended benefits of testing the reduced server are. But I do have an initial impression/thought.

Since you can mock credentials easily, this seems like a good excuse to keep the authentication config present, since the strategies are so easy to bypass. One reason to keep authentication enabled on each route during testing is that scope and entity-type are still validated whether or not you bypass the strategies. Authentication state is still managed too– if any of your request lifecycle hooks care about request.auth.isAuthenticated, for example, then you'll want to make sure that's being set just how it would on your "full" server. In short, not all authentication logic is bypassed when setting credentials via server.inject()– just your auth strategies are bypassed.

@mattiloh
Copy link
Author

mattiloh commented Jan 8, 2016

Thanks for the quick response! Yes, that's true, I only bypass the authentication-strategy, which is the same for every route ('hapi-auth-jwt2'). Sorry for confusing the terms logic and strategy. As the authentication-strategy is tested on its own, it should be safe to bypass it for the route-tests, I guess. (Right?)

The intended benefits are more focused and better readable tests. At the moment my tests look like this:

suite('/images/', function () {
    ...
    test('POST-request with valid data', function (done) {
        // add test user to database
        addUser()
        .then((user) => {
            // authenticate user by creating a valid JSON Web Token
            return authenticate(user);
        })
        .then((user) => {
            const request = {
                method: 'POST',
                url: '/images/',
                headers: {
                    'Cookie': user.jwtCookie, // apply JWT as cookie
                },
                payload: {
                    'metaData': {
                        'title': 'Test Title',
                        'location': 'Rome',
                    },
                },
            };

            return server.injectThen(request);
        })
        .then((response) => {
            // do assertions on response
            ...
            done();
        })
        .catch(done);
    });
    ...
});

authenticate(user) adds a JWT to the user's object with authentication-credentials dependent on the user's role, which will be authenticated by the auth-strategy 'hapi-auth-jwt2'. If I use the credentials option in request instead, I could remove that line and add very transparently which credentials the user has for a certain test.

Another benefit is, that the server-initialization might be faster, if I only add the necessary routes.

But maybe its better to take the middle by only adding the auth-strategy and the route-plugin to test?

What about the collision of routes? Does hapi detect possible collisions on server start?

@devinivy
Copy link

devinivy commented Jan 8, 2016

What you're saying sounds right to me– if you're testing your auth strategy on its own, then it's fairly safe to bypass that auth strategy in other tests by setting credentials during inject(). What I don't understand is how this implies that you need to or should only load part of your server during tests, but I think the reason I'm confused is just because I don't know how you've organized your server into plugins. As long as your routes are using the same auth config in tests and in production, you should be okay mocking the credentials.

I don't know of any way for routes to collide– though not often stated, it is a top priority of hapi's to avoid route collision. The router (hapijs/call) would throw an error as soon as a conflicting route is added.

@mattiloh
Copy link
Author

mattiloh commented Jan 8, 2016

So far I organized every main-resource as plugin. E.g. one plugin for the resource /users/, containing all sub-routes like /users/{id}, /users/{id}/password etc. Another plugin for the resource /images/ and all it's sub-routes. As long as I organize my routes like this, collisions between different plugins should easily be avoided, as every plugin has its own root-route.

But it's good to know about this collision-feature, thanks for the hint!

And thank you very much for clearing things up, Devin!

@mattiloh mattiloh closed this as completed Jan 8, 2016
@devinivy
Copy link

devinivy commented Jan 8, 2016

Cheers! :)

@devinivy devinivy self-assigned this Jan 8, 2016
@danielo515
Copy link

Hello,

I'm doing something very similar to what it is being done here. I also start a minimal server with hawk as authentication strategy. I'm injecting the credentials in the request that I'm injecting to the server, as it is described here.

I'm not the author of the endpoint that I'm testing, so I'm not sure about the motivations behind this, but the end point depends on the existence of an artifact property inside the credentials. It makes this check

if (typeof request.auth.artifacts !== 'undefined') {
                        id = request.auth.artifacts.id;
                    }

Such artifact seems to be injected in the validation process of happi. Could someone tell me if this is a bad practice (for me it looks like) and if not, how can I simulate such thing?
Thanks and regards

@devinivy
Copy link

devinivy commented Oct 7, 2016

@danielo515 server.inject() may also be passed artifacts when passing credentials. Does that answer your question?

@danielo515
Copy link

Dear @devinivy,
Thank you very much! I didn't know. I'll try it out and update with my findings.

Regards

@danielo515
Copy link

By the way, could you provide an example? Is just an artifacts property like a do for the credentials? Or is something else?

@devinivy
Copy link

devinivy commented Oct 7, 2016

server.inject({/* ... */, credentials: {}, artifacts: {} }, (res) => {/* ... */})

The inject() options are well-outlined in the API docs, where you can see it includes artifacts :)

@danielo515
Copy link

Hello @devinivy your suggestion worked like a charm. Thank you very much.

sharkinsspatial referenced this issue in AnkitaKhurana/oam-catalog Jul 24, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants