-
Notifications
You must be signed in to change notification settings - Fork 2
Synchronizing Datastore Updates for API Testing
In order to test the search endpoints for the API, we need to populate a datastore with test data. Our test framework's beforeEach function will clear the datastore, so each search test is responsible only for:
- Inserting the test data (an HTTP POST)
- Performing the search (an HTTP GET with query parameters)
- Asserting
The test data looks like this:
var testCoupons = [
{issuer: "target", value: "Free TV", promoCode: "XJSD32", ...},
{...},
{...},
{...},
{...}
];And here's an example of one such test. See how we need to do our GET in the callback of the insertCoupons function that we need to write. Obviously we can't just hardcode a bunch of POSTs followed by a GET; that would be utterly and completely wrong.
it('should return one expired coupon', function (done) {
insertCoupons(testCoupons, function () {
request(url).get('/coupons?status=expired').end(function (err, res) {
should.not.exist(err)
res.should.have.status(200)
couponsShouldBeSame(res.body[0], testCoupons[3])
res.body.length.should.equal(1);
done();
})
})
}); So do we write insertCoupons such that it only calls its callback after all coupons have been inserted?
Here we'll show three ways to do this. The first two are terrible; the third way is acceptable. We'll talk about a fourth way.
Here's our first try. This code SHOULD MAKE YOU RUN SCREAMING IN HORROR! It doesn't even do what we want, which is to pass in any array of coupons.
function insertTheFiveCouponsAndThen(callback) {
request(url).post('/coupons').send(testCoupons[0]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
request(url).post('/coupons').send(testCoupons[1]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
request(url).post('/coupons').send(testCoupons[2]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
request(url).post('/coupons').send(testCoupons[3]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
request(url).post('/coupons').send(testCoupons[4]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
callback();
});
});
});
});
});
}The code is wetter than a ... (here we need a Dr. Dorin analogy). Not only is it horribly copy-pasted, it is fixed to have size 5. What if we needed a different test with seven coupons? You can probably come up with a few more criticisms. In the words of Florian Bellanger, "This is just a stupid chocolate cupcake." Wait, I mean, "I don't like it."
Let's make it drier! How about this?
function insertCoupons(coupons, callback) {
if (coupons.length === 0) {
callback();
} else {
request(url).post('/coupons').send(coupons[0]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
insertCoupons(coupons.slice(1), callback);
});
}
}Better, more than just a stupid chocolate cupcake, but I still don't like it. All of the coupons are inserted sequentially. And that slice(1) in there. Uff. Makes a copy of the "rest of the list" each time. Let's try once more.
function insertCoupons(coupons, callback) {
var couponsRemaining = coupons.length;
if (couponsRemaining === 0) {
callback();
}
for (var i = 0; i < coupons.length; i++) {
request(url).post('/coupons').send(coupons[i]).end(function (err, res) {
should.not.exist(err);
res.should.have.status(201);
couponsRemaining--;
if (couponsRemaining === 0) {
callback();
}
});
}
}This looks better. The callback — the thing that does the GET followed by the assertions — is done in the callback of the last POST. That wasn't so bad, was it? Well, perhaps we can get rid of the non-DRY check for zero and callback invocation.
Maybe someone already has an npm package for this?