Skip to content

Instantly share code, notes, and snippets.

@nebez
Created September 11, 2018 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nebez/2b5e6fcc34c5197f6197ddcd7afbbb32 to your computer and use it in GitHub Desktop.
Save nebez/2b5e6fcc34c5197f6197ddcd7afbbb32 to your computer and use it in GitHub Desktop.
import { expect } from 'chai';
import * as Str from 'src/Support/String';
const app: any = {};
const otherMumboJumbo: any = {};
const validStatusCode = 200;
const invalidStatusCode = 500;
describe('String utility', () => {
describe('slugify', () => {
// This test fails too early and the output is not informative enough.
// If there are tons of test cases, let's refactor this into a "data
// provider"-esque test so we can see the pass/fail of each.
it('should work on all string inputs', () => {
// Let's test some ascii as well as some non-ascii encodings.
expect(Str.slugify('this is a test input')).to.equal('this-is-a-test-input');
expect(Str.slugify('thiś iś à teśt input')).to.equal('this-is-a-test-input');
expect(Str.slugify('Ťhîś îś even more')).to.equal('this-is-even-more');
expect(Str.slugify('ŠoḾe MØRE')).to.equal('some-more');
});
// This test has a garbage failure message.
it('should coalesce consecutive hyphens', () => {
expect(Str.slugify('coalesce me') === 'coalesce-me').to.equal(true);
});
});
describe('prepareForQuery', () => {
// This test does not let the writer of the test enforce the output.
// It is a fragile test that, when it fails, provides no confidence or
// hints at what the real biz-requirement-determined output should be.
it('should encode special characters after slugification', () => {
// According to my biz requirements, an input can only be
// "prepped" for a query parameter after it has been both
// slugified and percent-encoded (in that order).
const input = 'Contact us at support@ssense.com';
const expectedCleanedInput = encodeURIComponent(Str.slugify(input));
expect(Str.prepareForQuery(input)).to.equal(expectedCleanedInput);
});
});
describe('validateUrl', () => {
it('should validate both URL schemas', () => {
expect(() => Str.validateUrl('http://example.com')).to.not.throw();
expect(() => Str.validateUrl('https://example.com')).to.not.throw();
});
// This is a bad example of a test that only checks if the method
// throws, but not -what- it throws. The issue here is that the
// function takes an array of strings too but will throw on the first
// string occurrence. Display the problems here with first changing
// the input to an array and showing that the function throws. Add
// strict error message checking but then change the message to show
// that's not enough because it's too coupled to the implementation.
// Then finally use a combination of the Error constructor check + a
// regex.
it('should throw errors for invalid URLs', () => {
expect(() => Str.validateUrl('foo://example.com')).to.throw();
});
});
});
describe.skip('My web app', () => {
// The problem with this test is multi tiered.
// Firstly, the use of constants declared outside of the scope of the
// tests is problematic. This is one (of many) ways to introduce global
// state and leaky tests. You may have heard of the "runInBand" option
// provided by Jest which lets tests run serially in the same node
// process. While running in separate processes avoids test suites leaking
// into eachother, this is a bad idea to rely on the implementation
// details of your test runner to prevent you from writing leaky state.
// Secondly, using a named constant at the top-level to define something
// easily inferrable at the test level is bad (validStatusCode and
// invalidStatusCode). We know what good at bad HTTP status codes are.
// Inline them.
// Thirdly, using constants within a test to de-duplicate an identifier is
// unnecessary for these kinds of small examples. Having the magic number
// 99 within the test is very obvious that it's purposeful. The exception
// to this rule is when the magic number is something common like 0 or 1.
// This can make use of a good variable named "BrandIdentifier" or
// something.
// And finally, the test is all over the place. Tests should be focused.
// We're bootstrapping the application and have lost some contextual space
// here to that. We're running an assertion on a useless property, aka the
// length of the products array returned. This is not the point of the
// test according to the name, we care about HTTP status codes. Then the
// test proceeds to hit two different endpoints.
it('should return appropriate status codes for valid requests', async () => {
const goodBrandId = 99;
otherMumboJumbo.setupRoutes(require('routes.json'));
otherMumboJumbo.setupMiddleware(require('middleware.js'));
app.addMumbuJumbo(otherMumboJumbo);
await app.initialize();
const res = await app.get(`/products?brand_id=${goodBrandId}`);
expect(res.status).to.equal(validStatusCode);
expect(res.body.meta.brand).to.equal(goodBrandId);
expect(res.body.products.length).to.be.greaterThan(1);
const res2 = await app.get(`/products?brand_id=`);
expect(res2.status).to.equal(invalidStatusCode);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment