Mocha: How to test Express rendered views - node.js

-- Background
I'm trying to test an Express application. This is a hobby project for me, so I have not used Express to serve a JSON api (as it is usually done).
-- The Problem
The issue is, I can't figure out a way to test the view's content to ensure that the page is actually being rendered against the view file in the project itself.
-- What I've Tried
For instance, I can't test a simple index page, because the jade file starts with extends layout. This makes it difficult to test if dynamic content is being rendered.
Does anyone have suggestions for testing whether the view is actually being rendered?
it ('renders the index page', function(done) {
var jade = require('jade');
var should = require('should');
var fs = require('fs');
supertest(app)
.get('/')
.expect(200)
.end(function(err, res) {
var rawJade = fs.readFileSync('./views/index.jade').toString();
res.text.should.equal(rawJade.convertToHtml()); // jade supports a function like this
});
)};

There are, as usual, several ways to attack this problem. Before I continue, I encourage you to ask yourself why you need to do this. Tests like this are extremely fickle. If you make some tiny change, it is going to force you to rewrite tests that are now failing.
That said, I think the easiest way to start adding tests that assert proper rendering is with https://github.com/cheeriojs/cheerio
A basic example would look like the following:
it ('renders the index page', function(done) {
var should = require('should');
var cheerio = require('cheerio');
supertest(app)
.get('/')
.expect(200)
.end(function(err, res) {
err.should.not.be.ok();
res.should.be.ok();
var $ = cheerio.load(res.body);
var header = $('h1:first');
header.should.equal('Hello World!');
done();
});
)};
Now you aren't going to be testing whether the rendered view looks exactly like what you want (I mean you could but that would be tedious). But that also means that if you make some small insignificant change, the whole thing won't come crumbling down. Instead you can focus on testing whether key aspects of your UI are rendering properly (e.g. The title of the page is there, with the correct spelling and class/id properties)

Related

using HTTP.request in ejs render

example in Express I have a route that linked to my ejs middleware.
Code 1 :
app.all("/sample", function(req,res,next){
ejs.renderFile("./sample.ejs", {req,res,next,require,module:require("module")} {}, function(e, dt){
res.send(dt.toString());
});
});
everything fine in the first code. and in sample.ejs (second code) I want to request to some text file in Internet and return to HTML (and should use HTTP module)
Code 2:
<%
var http=require("http");
var url=require("url");
var opt = url.parse("http://website.com/thisfile.txt");
/* it will return "Hello World!" btw */
var dt = ""
var hReq = http.request(opt, function(hRes){
hRes.on("data", function(chunk){
dt+=chunk.toString();
});
});
hReq.end();
%>
<h2>Here is the data is <%= dt %></h2>
and while i try to my browser. it just give me
Code 3:
<h2>Here is the data is </h2>
where I want it gave me
Code 4:
<h2>Here is the data is Hello World!</h2>
How could I get that?
I just want to use HTTP Module or Net Socket Module. and I just want to edit the Code 2. Code 1 is permanently like that.
While EJS can run full JavaScript, you generally want to leave as much as possible out of the template and put more of your logic in your main express request handler.
Since the rendering is done server side anyway, nothing will change other than making it easier to read and test.
You should consider moving the HTTP request made in your EJS template into your app.all('/sample') handler and then just inject the result into your template. In this case that would be the final string collected from the HTTP request. You'll then end up with something like this. (This is untested code).
Also, while it is not required at all, I'd suggest taking a look at something like the request, this makes HTTP requests much easier!
var request = require('request');
app.all("/sample", function(req,res,next){
// Make the HTTP request
request('http://www.website.com/file.txt', function(err, response, body) {
// Render the ejs template
ejs.renderFile("./sample.ejs", {file: body}, function(e, dt) {
// Send the compiled HTML as the response
res.send(dt.toString());
});
});
});

How would I test sending a route with mocha/chai/superagent in express?

it('sends login page if we\'re logged out at /', function (done) {
superagent.get('http://localhost:4000/').end(function(err, res){
if(err) {return done(err)}
expect(res).to.have.property('status', 200);
//how would i do something like this?
expect(res.viewRendered).to.be.equal('index.ejs')
done();
});
});
I'm new to testing and I hate it.. a lot. I'm trying to learn the basics and it's the most frustrating learning curve I've ever experienced. I've been looking up documentation for hours and still haven't been able to figure out how to check which route has been rendered
I would go about this another way: instead of relying on the output of the request, and match that up against a template (which can be quite difficult unless you add some sort of identifier to each template, which doesn't feel entirely right), you can take advantage of some internals of Express, and specifically on how it renders templates.
The Express documentation states the following (here):
Express-compliant template engines such as Pug export a function named __express(filePath, options, callback), which is called by the res.render() function to render the template code.
You are not using Pug, but EJS, but the same principle applies: the ejs module exports a function called __express which will be called with full path of the template that should be rendered. And that also happens to be what you want to test for!
So the question now becomes: "how can you test that ejs.__express() gets called with the proper template name?". Answer: you can spy on it.
My favorite module for this is Sinon, so the example below will use that. Sinon is great to spy on existing functions, or to make them do entirely different things if you want.
As an example, I will use the following, very simple, Express app:
// app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.render('index.ejs', { foo : 'bar' });
});
module.exports = app;
We want to test if, when / is requested, the template index.ejs gets rendered.
Instead of using superagent, I'll be using supertest, which is meant for testing HTTP apps.
Here's the annotated Mocha test file:
// Import the Express app (from the file above), which we'll be testing.
const app = require('./app');
// Import some other requirements.
const expect = require('chai').expect;
const supertest = require('supertest');
const sinon = require('sinon');
// Import the EJS library, because we need it to spy on.
const ejs = require('ejs');
// Here's the actual test:
it('sends login page if we\'re logged out at /', function (done) {
// We want to spy on calls made to `ejs.__express`. We use Sinon to
// wrap that function with some magic, so we can check later on if
// it got called, and if so, if it got called with the correct
// template name.
var spy = sinon.spy(ejs, '__express');
// Use supertest to retrieve / and make sure that it returns a 200 status
// (so we don't have to check for that ourselves)
supertest(app)
.get('/')
.expect(200)
.end((err, res) => {
// Pass any errors to Mocha.
if (err) return done(err);
// Magic! See text below.
expect(spy.calledWithMatch(/\/index\.ejs$/)).to.be.true;
// Let Sinon restore the original `ejs.__express()` to its original state.
spy.restore();
// Tell Mocha that our test case is done.
done();
});
});
So what's this magic:
spy.calledWithMatch(/\/index\.ejs$/)
It means: "return true if the function that was being spied on (ejs.__express()) got called with a first argument that matches the regular expression \/index\.ejs$". Which is what you want to test for.
The reason I'm using a regular expression here is because I'm lazy. Because the first argument (filePath in the quote above) will contain the full path to the template file, it can be quite long. You can directly test for it if you want:
spy.calledWith(__dirname + '/views/index.ejs')
But that would break if the location of the template directory got changed. So, like I said, I'm lazy, and I'll use a regular expression match instead.
With tools like supertest, sinon and chai, testing can actually become fun (honest!). I have to agree that the learning curve is rather steep, but perhaps that an annotated example like this can help you get a better idea on what's possible and how to go about it.

Cascade-like rendering with Express JS

With an express app running on a node server, how would I go about recursively searching for a render file from the full path right back to the beginning of the supplied URL.
For example, if someone was to hit my server with www.somewebsite.com/shop/products/product, the render engine would first check that there is an index.jade file in shop/products/product/. If none is found it would then check shop/products/, and subsequently shop/.
var express = require('express');
var app = express();
app.get('/*', function(req, res){
res.render(req.path + '/index.jade', function(err, html){
// some loopback code which alters the path and recalls the render method
})
});
The problem is that the response object is not passed to the render callback, so I'm unable to recall render on the response. I'm looking to create a loop because the URL paths may be any number of directories deep, so I can't just assume I only need to cascade for a definitive number of times.
Anyone see a way round this?
You should be able to use the response object from the closure. I think (assuming express allows you to call res.render a second time) you could use code like this answer to achieve what you want:
var express = require('express');
var app = express();
app.get('/*', tryRender);
function tryRender(req, res){
res.render(req.path + '/index.jade', function(err, html){
if (err) {
req.path = 'mynewpath';
tryRender(req, res);
}
})
}
Note: You will need to add a base case or this function will recurse infinitely if it doesn't find a view that works :D
In the event that express doesn't allow a subsequent call to res.render, you'll probably need to find out if the file exists on the file system yourself.

How can I bootstrap models from express js into backbone at page load?

I have some data in a mongodb database and want to pass it to a backbone collection when I load the home page. One way of doing this would be to set up a node route like this:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.send(docs_string);
};
};
But this won't work because it won't render my jade template that pulls in the backbone code, it simply shows the JSON in plain text.
Alternatively, I could render my jade template passing in the data as a variable to jade:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.render('index', {
title: "Data",
docs_string: docs_string
})
});
};
Then in the jade template, have a script like this to add the users to my user collection:
script
var docs = !{docs_string};
var users = new app.Users();
_.each(docs, function(doc) {
var user = new app.User(doc);
users.add(user);
})
But this seems wrong, since I don't really want to pass the data to the jade template, I want to pass it to a backbone collection. Also, with this solution I don't know how to then include an underscore template (on the backbone side of things) into the page rendered by jade on the server side.
What is the standard way of passing data from a node server to a backbone collection?
Assuming your data is an object, you should convert it to string using JSON.stringify() and then insert in a page inside script tag, so your resulting HTML looks like this (I don't use Jade):
<script>
var data = {...}; // in template instead of {...} here should be the instruction to insert your json string
</script>
Then when the page loads, your script will be executed and the data will be available as a global variable in the browser so you can initialise backbone collection using it. This all is a good idea only to bootstrap your data on the first page load (to avoid extra request) and then use API to request data for this and other pages.
Check out Steamer, a tiny node / express module made for this exact purpose.
https://github.com/rotundasoftware/steamer

Is it OK to add data to the response object in a middleware module in Express.js?

Here's the basic setup. I'm trying to create a simple middleware component that would allow me to easily pass data from my route directly to my javascript in the client side. (Very similiar to the Gon gem in ruby). The way I'm doing it is by having a module that looks like this:
module.exports = function(){
return function(req,res,next){
var app = req.app;
if(typeof(app) == 'undefined'){
var err = new Error("The JShare module requires express");
next(err);
return;
}
res.jshare = {};
app.dynamicHelpers({
includeJShare: function(req,res){
if(typeof(res.jshare) === 'undefined'){
return "";
}
return function(){
return '<script type="text/javascript">window.jshare=' + JSON.stringify(res.jshare) + '</script>';
}
}
});
next();
};
}
Then, in my route I can do this:
exports.index = function(req, res){
res.jshare.person = {firstName : "Alex"};
res.render('index', { title: 'Express' })
};
Finally in the layout.jade:
!{includeJShare()}
What that does is in outputs a line of javascript on the client that creates the exact JSON object that was created server side.
Here's the question; it all works as expected, but being new to Express and Node.js in general, I was just curious if attaching properties onto the response object is OK, or is there something wrong with doing it that I'm simply overlooking? For some reason it doesn't pass my "smell test" but I'm not sure why.....
I know this is an old thread, but there is something else to add to this topic.
Express has a response.locals object which is meant for this purpose - extending the response from middleware to make it available to views.
You could add a property directly to the response object, and as #hasanyasin indicated, is how JavaScript is designed. But Express, more specifically, has a particular way they prefer we do it.
This may be new in express 3.x, not sure. Perhaps it didn't exist when this question was asked.
For details, see
http://expressjs.com/en/api.html#res.locals
There is also an app.locals for objects which don't vary from request to request (or response to response I suppose).
http://expressjs.com/en/api.html#app.locals
See also: req.locals vs. res.locals vs. res.data vs. req.data vs. app.locals in Express middleware
It is perfectly OK. It is how JavaScript is designed. Only thing you should be careful is to not accidentally overriding already existing properties or being overridden by others. To be safer, instead of adding everything directly to req/res objects, you might consider going a level deeper:
res.mydata={}
res.mydata.person= ...
Like that.
Use res.locals for including custom variables in your response object.

Resources