This post highlights snapshots on best practices/hacks, to code, test, deploy and to maintain large-scale nodejs
apps. It provides big lines on what became a book on testing nodejs
applications.
If you haven't yet, read the How to make nodejs
applications modular article. This article is an overall follow-up.
Like some of the articles that came before this one, we are going to focus on a simple question as our north star: What are the most important questions developers have when testing a nodejs
application? When possible a quick answer will be provided, else we will point in the right direction where information can be found.
In this article we will talk about:
Even though this blog post was designed to offer complementary materials to those who bought my Testing nodejs
Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book.
Show me the code
var express = require('express'),
app = express(),
server = require('http').createServer(app);
//...
require('./config');
require('./utils/mongodb');
require('./utils/middleware')(app);
require('./routes')(app);
require('./realtime')(app, server)
//...
module.exports.server = server;
Example:
The code provided here is a recap of How to make nodejs
applications modular article. You may need to give it a test drive, as this section highlights an already modularized example.
Testing
Automation is what developers do for a living. Manual testing is tedious, repetitive, and those are two key characteristics of things we love automating. Automated testing is quite intimidating for newbies and veterans alike. Testing tends to be more of an art, the more you practice, the better you hone your craft.
In the blogosphere, – My node
Test Strategy ~ RSharper Blog. – nodejs
testing essentials
BDD versus TDD
Why should we even test
Testing is unanimous within the developers community, the question always is around how to go about testing.
There is a discussion mentioned in the first chapter between @kentbeck, @martinfowler and @dhh that made rounds on social media, blogs and finally as a subject of reflection in the community. When dealing with legacy code, there should be a balance and only adopt tdd
as one tool in our toolbox.
In the book we do the following exercise alternative to classic tdd
: read, analyze, modify if necessary, rinse and repeat. We cut the bullshit, and get to test whatever needs to be tested, and let nature take its course.
One thing is clear: We cannot guarantee the sanity of a piece of code unless it is tested. The remaining question is on “How” to go about testing.
There is a summary of the discussions mentioned earlier, titled Is TDD Dead?. In the blogosphere, – BDD-TDD ~ RobotLovesYou Blog. – My node
Test Strategy ~ RSharper Blog – A TDD Approach to Building a Todo API Using nodejs
and mongodb
~ SemaphoreCI Community Tutorials
What should be tested
Before we dive into it, lets re-examine pros and cons of automated tests — in the current case, Unit Tests.
Pros:
- Steer release confidence
- Prevents common use case and unexpected bugs
- Help project's new developers better understand code
- Improves confidence when refactoring code
- Well tested product guarantees improves customer experience
Cons:
- Take time to write
- Increase learning curve
At this point, if we agree that the pros outweigh the cons, we can set an ideal of testing everything. Those are features of a product or functions of code. Re-testing large applications manually are daunting, exhausting, and sometimes simply not feasible.
The good way to think about testing is not by thinking in terms of layers(controllers, models, etc.). Layers tend to be bigger. It is better to think in terms of something much smaller like a function(TDD way) or a feature(BDD way).
Brief, every controller/business logic/utility libraries/nodejs
servers/routes all features are also set to be tested ahead of release.
There is an article on this blog that gives more insight on — How to create good test cases (Case > Feature > Expectations | GivenWhenThen) — titled “How to write test cases developers will love reading”. In the blogosphere, – Getting started with nodejs
and mocha
There is no shortage of tools in nodejs
community. The problem is analysis paralysis. Whenever the time comes to choose testing tools, there are layers that should be taken into account: test runners, test doubles, reporting, and eventually, if there is any compiler that needs to be added in the mix.
Other than that, there is a list of a few things to consider when choosing a testing framework: – Learning curve – How easy to integrate into project/existing testing frameworks – How long does it take to debug testing code – Choice of the testing framework, and other testing tools consider – How good is documentation – How big is the community, and how good is the library maintained – What is may solve faster(Spies, Mocking, Coverage reports, etc) – Instrumentation and test reporting, just to name a few.
There are sections dedicated to providing hints and suggestions throughout the book. There is also this article “How to choose the right tools” on this blog that gives a baseline framework to choose, not only for testing frameworks but any tool. Finally, In the blogosphere, – jasmine
vs. mocha
, chai
and sinon
. – Evan Hahn has pretty good examples of the use of test doubles in How do I jasmine
blog post. – Getting started with nodejs
and jasmine
– has some pretty amazing examples, and is simple to start with. – Testing expressjs
REST APIs with Mocha
Testing servers
The not-so-obvious part when testing servers is how to simulation of starting and stopping the server. These two operations should not bootstrap dependent servers(database, data-stores) or make side effects(network requests, writing to files) to reduce the risk associated with running an actual server.
There is a chapter dedicated to testing servers in the book. There is also this [article on this blog that can give more insights](). In the blogosphere, – How to correctly unit test express server – There is a better code structure organization, that makes it easy to test and get good test coverage on “Testing nodejs
with mocha”. – How to correctly unit test express server
Testing modules
Testing modules is not that different from testing a function, or a class. When we start looking at this from this angle, things will be a little easy.
The grain of salt: a module that is not directly a core component of our application, should be left alone and mocked out entirely when possible. This way we keep things isolated.
There are dedicated sections in every chapter about modularization, as well as a chapter dedicated to testing utility libraries(modules) in the book. There is also an entire series of articles — a more theoretical: “How to make nodejs
applications modular and a more technical: “How to modularize nodejs
applications” — on this blog modularization techniques. In the blogosphere, – Export This: Interface Design Patterns for nodejs
Modules Alon Salant, CEO of Good Eggs and nodejs
module patterns using simple examples by Darren DeRider – How to modularize your Chat Application
Testing routes
Challenges while testing expressjs
Routes
Some of the challenges associated with testing routes are testing authenticated routes, mocking requests, mocking responses as well as testing routes in isolation without a need to spin up a server. When testing routes, it is easy to fall into integration testing trap, either for simplicity or for lack of motivation to dig deeper.
Integration testing trap is When a developer confuses integration test(or E2E) with unit test, and vice versa. The success of a balanced test coverage identifies sooner the king of tests adequate for a given context, what percentage of each kind of tests.
For a test to be a unit test in route testing context, there will be – Focus to test code block(function, class, etc), not the output of a route – Mock requests to third party systems(Payment Gateway, Email Systems, etc) – Mock database read/write operations – Test worst-case scenario such as missing data and data-structure
There is a chapter dedicated to testing models in the book. There is also this article “Testing expressjs
Routes” on this blog that gives more insight on the subject. In the blogosphere – A TDD approach to building a todo API using nodejs
and mongodb
– Marcus on supertest
~ Marcus Soft Blog
Testing controllers
When modularizing route handlers, there is a realization that they may also be grouped into a layer of their own, or event classes. In MVC jargon, this layer is also known as the controller layer.
Challenges testing controllers, by no surprise, are the same when testing expressjs
route handlers. The controller layer thrives when there is a service layer. Mocking database read/write operations, or service layers, that is not core/critical to validation of the controller's expectations are some of such challenges.
Mocking controller request/response objects, and when necessary, some middleware functions.
There is a chapter dedicated to testing controllers in the book. There is also this article Testing nodejs
controllers with expressjs
framework on this blog that gives more insight on the subject. In the blogosphere, – This article covers Mocking Responses, etc — How to test express controllers.
Testing services
There are some instances where adding a service layer makes sense.
One of those instances is when an application has a collection of single functions under utility(utils). Chances are some of the functions under the utility umbrella may be related in terms of features, the functionality they offer, or both. Such functions are good to use case to be grouped under a class: service
Another good example is for applications that heavily use the model. Chances are the same functions can be re-used in multiple instances, and fixing an issue involves multiple places to fix as well. When that is the case, such functions can be grouped under one banner, in such a way that an update to one function, gets reflected in every instance where the function has been used.
From these two use cases, the testing service has no one-size fit-all testing strategy. Every case of service should be dealt with depending on the context it is operating in.
There is a chapter dedicated to testing services in the book. In the blogosphere, – “Building Structured Backends with nodejs
and HexNut” by Francis Stokes ~ aka @fstokesman on Twitter source ...
Testing middleware
The middleware in a sense are hooks that intercept, process and forward the result to the rest of the route in the expressjs
(connectjs
) jargon. It is by no surprise that testing middleware shares the same challenges as testing route handlers and controllers.
There is a chapter dedicated to testing middleware in the book. There is also this article “Testing expressjs
Middleware” on this blog that gives more insight on the subject. In the blogosphere, – How to test expressjs
controllers
Testing asynchronous code
Asynchronous code is a wide subject in nodejs
community. Things ranging from regular callbacks, promises, async/await constructs, streams, and event streams(reactive) are all under an asynchronous umbrella.
Challenges associated with asynchronous testing, depending on the use case and context at hand. However, there are striking similarities say, testing testing async/await
versus a promise.
When an object is available, it makes sense to get a hold on it, execute assertions once it resolves. That is feasible for promises, streams, async/await construct. However, when the object is some kind of event, then the hold on the object can be used to add a listener and assert once the listener is resolved.
There are multiple chapters dedicated to testing asynchronous code in the book. There are also multiple article on this blog that gives more insight on the subject such as – “How to stub a stream
function” – “How to Stub Promise Function and Mock Resolved Output” – “Testing nodejs
streams”. In the blogosphere, – []()
Testing models
testing models goes hand in hand with mocking database access functions
Functions that access or change database state can be replaced by spy fakes, custom function replacements capable to supply|emulate similar results as replaced functions.
sinon
may not make unanimity, but is a feature-complete battle-tested test double library, amongst many others to choose from.
There is a chapter dedicated to testing models in the book. There is also this article []() on this blog that gives more insight on the subject. In the blogosphere, – Mocking/Stubbing/Spying mongoose models – stubbing mongoose model question and answers on StackOverflow – Mocking database calls by wrapping mongoose
with mockgoose
Testing WebSockets
Some of the challenges testing WebSockets can be summarized as trying to simulate: – sending and receiving a message on the WebSocket
endpoint.
There is a chapter dedicated to testing WebSockets in the book. There is also this article on this blog that can give more ideas on how to go about testing WebSocket endpoints — another one on how to integrate WebSockets with nodejs
. Elsewhere in the blogosphere, – Testing socket.io
with mocha
, should.js
and socket.io
client – sharing session between expressjs
and socket.io
Testing background jobs
The background jobs bring batch processing to the nodejs
ecosystem. Background jobs constitute a special use case of asynchronous communication that spans time and processes on which the system is running on.
Testing this kind of complex construct, require distilling the fundamental work done by each function/construct, by focusing on the signal without losing the big picture. It requires quite a paradigm shift(word used with reservation).
There is a chapter dedicated to testing background jobs in the book. There is an article Testing nodejs
streams on this blog that gives more insight on the subject. In the blogosphere, – Mocking/Stubbing/Spying mongoose
models ~ CodeUtopia Blog
Conclusion
Some source code samples came from QA sites such as StackOverflow, hackers gists, Github documentation, developer blogs, and from my personal projects.
There are some aspects of the ecosystem that are not mentioned, not because they are not important, but because mentioning all of them can fit into a book.
In this article, we highlighted what it takes to test various layers, at the same time make a difference between BDD/TDD testing schools. There are additional complimentary materials in the “Testing nodejs
applications” book.
References
#snippets #nodejs #testing #tdd #bdd