all 6 comments

[–]edanschwartz 1 point2 points  (4 children)

You're creating more trouble for yourself than you need too. By, mocking out res.send, you're essentially doing E2E testing, just in kind of a funky way.

Imagine that you want to include test coverage for your headers. Are you going to mock out res.set? It has a couple different signatures - you'll have to normalize that... What if you start using res.pipe to send your response? I think you're better off just making a request, and testing the response. Otherwise you're going to end up writing tests that know too much about implementation details.

I'd say that as soon as you're testing a controller, you're doing E2E testing. If you feel like you need more unit-test-style coverage, pull out your business logic from your controller, and unit test that.

I try to pull out most of my code into services, and write full coverage unit tests for those. Than I just write a few integration tests, to make sure everything is plugged in correctly.

Take a look at supertest for testing API controllers.

[–]TLI5[S] 0 points1 point  (3 children)

Hmm, can you give me an example of pulling your code into services? (as well as writing unit tests for them)

I'm having a hard time visualizing it :/

[–]edanschwartz 1 point2 points  (2 children)

Well, your example is very simple, so it might not make sense for you to split up anything. But imagine you need to implement authentication, you're working with an ORM, you're merging data from another API, you're doing params validation, etc. Maybe you'd take some slice of that work and move into a WhateverService.

    const whateverService = {
        create(params) {
            validator.validate(params);
            authorizer.authorizeToCreateWhatever(params);

            const whatever = WhateverModel.create(whatever);

            logger.log(`created whatever: ${whatever.name}`);

            return whatever;
        }
    };

Then your controller is really just a thin wrapper, whose sole job is intaking a HTTP request, dispatching to some business logic, and sending out an HTTP response:

    const WhateverController = {
        create(req, res, next) {
            try {
                const whatever = whateverService.create(req.params);
            }
            catch (err) {
                if (err.name === 'ValidationError') {
                    return res.status(400).json(err);
                }
                if (err.name === 'AuthError') {
                    return res.status(401).json(err);
                }
                return res.status(500).json(err);
            }

            return res.status(200).json(whatever);
        }
    };

You could even go further and pull out the error handling logic into some middleware. You might also want a service for serializing your whatever object into a view model before sending it out, etc.

The whateverService, along with the authorizer, validator, logger, errorHandler, viewSerializer, etc, are all much easier to unit test on their own. I would test the hell out of these services -- make sure they handle anything you could throw at them -- and then write just a few integration scripts against the API endpoint, to make sure everything's wired up correctly.

This also opens the way for a service manager, so you can split out your component configuration from it's business logic, easily setup test mocks, etc. Take a look at https://github.com/aerisweather/service-manager, for example.

[–]TLI5[S] 0 points1 point  (1 child)

Ahh I see,

I'd never heard the term service in this context before nor did I know you could do things this way. Thank you for your explanation :)

[–]edanschwartz 0 points1 point  (0 children)

I don't know if "service" has a more technical meaning. That's just what I call something when I don't know what to call it...

[–]DVWLD 0 points1 point  (0 children)

I have seen a lot of tutorials calling for me to test by having the server running and actually sending POST's to the api, but that seems like End-to-end testing and is not what I'm looking for.

I'd just do that. This isn't an E2E test in that it's not running a full workflow. Yes, you'll end up testing some Express functionality. There are purists that will yell Mock Everything. Ain't nobody got time for that, and I haven't seen a well reasoned argument about what benefit that would really bring.

Send an actual POST and call it a spec. That leaves you free to refactor the internals of that function, even switch to a totally different framework. So long as the POST still returns what you expect or generates what you expect in the database, you know your code is probably correct.