Skip to content

Commit

Permalink
chapter 04: copy code from chapter 03 to the chapter 04 folder in ord…
Browse files Browse the repository at this point in the history
…er to continue with the book
  • Loading branch information
devcorpio committed Jan 12, 2019
1 parent 9f412b1 commit 36ad452
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ArgumentError extends Error {}

module.exports = ArgumentError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function alwaysValidFakeExtensionManager() {
/**
* @param {string} fileName
* @return {Promise}
*/
async function isValid(fileName) {
return true;
}

return {
isValid,
};
}

module.exports = alwaysValidFakeExtensionManager;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const fileExtensionManagerFactory = require('./fileExtensionManager');

function extensionManager() {
let customManager = null;

function create() {
if (customManager !== null) {
return customManager;
}

return fileExtensionManagerFactory();
}

function setManager(manager) {
customManager = manager;
}

return {
create,
setManager,
};
}

module.exports = extensionManager;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function fakeExtensionManager() {
/**
* @type {boolean}
*/
let valid = false;

/**
* @param {boolean} value
*/
function willBeValid(value) {
valid = value;
}

/**
* @param {string} fileName
*/
async function isValid(fileName) {
return valid;
}

return {
willBeValid,
isValid,
};
}
module.exports = fakeExtensionManager;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const fs = require('fs');
const util = require('util');

const readFilePromisied = util.promisify(fs.readFile);

function fileExtensionManager() {
/**
* @param {string} fileName
* @return {Promise}
*/
async function isValid(fileName) {
const fileNameExtensions = await readFilePromisied(
`${__dirname}/fileNameExtensions.config.json`,
'utf8'
).then(fileContent => JSON.parse(fileContent).extensions);

const isValidExtension = fileNameExtensions.some(
function checkFileNameExtension(extension) {
if (
fileName
.toUpperCase()
.endsWith(`.${extension.toUpperCase()}`)
) {
return true;
}

return false;
}
);

return isValidExtension;
}

return {
isValid,
};
}

module.exports = fileExtensionManager;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extensions": ["slf", "sql"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const ArgumentError = require('./ArgumentError');
const fileExtensionManagerFactory = require('./fileExtensionManager');

class LogAnalyzer {
constructor() {
/**
* @type {boolean}
*/
this.wasLastFileNameValid = false;
}
/**
* @return {boolean}
*/
getWasLastFileNameValid() {
return this.wasLastFileNameValid;
}

/**
* a virtual function created to use "Extract and Override" technique
*/
getManager() {
return fileExtensionManagerFactory();
}

/**
* @param {string} fileName
* @return {Promise}
*/
async isValidLogFileName(fileName) {
this.wasLastFileNameValid = false;

if (fileName === '') {
throw new ArgumentError('filename has to be provided');
}

const result = await this.getManager().isValid(fileName);

if (!result) {
return false;
}

this.wasLastFileNameValid = true;
return true;
}
}

module.exports = LogAnalyzer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const ArgumentError = require('./ArgumentError');

function logAnalyzer(extensionManager) {
/**
* @type {boolean}
*/
let wasLastFileNameValid;

/**
* @return {boolean}
*/
function getWasLastFileNameValid() {
return wasLastFileNameValid;
}

/**
* @param {string} fileName
* @return {Promise}
*/
async function isValidLogFileName(fileName) {
wasLastFileNameValid = false;

if (fileName === '') {
throw new ArgumentError('filename has to be provided');
}

const result = await extensionManager.isValid(fileName);

if (!result) {
return false;
}

wasLastFileNameValid = true;
return true;
}

return {
getWasLastFileNameValid,
isValidLogFileName,
};
}

module.exports = logAnalyzer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const logAnalyzerFactory = require('./logAnalyzer');
const fakeExtensionManagerFactory = require('./fakeExtensionManager');
const extensionManagerFactory = require('./extensionManager');

// imported to try the technique "Extract and override"
const TestableLogAnalyzerClass = require('./testableLogAnalyzer.class');

let myFakeExtensionManager;

beforeEach(() => {
myFakeExtensionManager = fakeExtensionManagerFactory();
});

describe.each([
['johndoe.js', false],
['johndoe.slf', true],
['johndoe.SLF', true],
])('isValidLogFileName("%s"))', (fileName, expected) => {
it(`bad extension returns ${expected}`, async () => {
myFakeExtensionManager.willBeValid(expected);

const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);
const result = await logAnalyzer.isValidLogFileName(fileName);
expect(result).toBe(expected);
});
});

describe('isValidLogFileName', () => {
it('empty filename throws error', async () => {
async function emptyLogFileName() {
const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);

return logAnalyzer.isValidLogFileName('');
}

await expect(emptyLogFileName()).rejects.toThrow(
'filename has to be provided'
);
});

/**
* an example of state-based testing
*/
it.each`
fileName | expected
${'johndoe.foo'} | ${false}
${'johndoe.slf'} | ${true}
`(
'when called there changes wasLastFileNameValid that returns $expected',
async ({ fileName, expected }) => {
myFakeExtensionManager.willBeValid(expected);

const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);
await logAnalyzer.isValidLogFileName(fileName);
const result = logAnalyzer.getWasLastFileNameValid();

expect(result).toBe(expected);
}
);

/**
* an example of use of "injecting a fake just before a method call"
* right now I'm not injecting a fake, and extensionManager is returning fileExtensionManager,
* therefore this test is an integration test and not a unit test!!!!
*/
it('return true using extensionManagerFactory', async () => {
const extensionManager = extensionManagerFactory();
const logAnalyzer = logAnalyzerFactory(extensionManager.create());
const result = await logAnalyzer.isValidLogFileName('johndoe.sql');

expect(result).toBe(true);
});

/**
* an example of use of "injecting a fake just before a method call"
* In this case I'm setting a fake extension manager, that converts this in a unit test!!, because
* right now I have not external dependencies.
*/
it('return true using extensionManagerFactory', async () => {
myFakeExtensionManager.willBeValid(true);
const extensionManager = extensionManagerFactory();
extensionManager.setManager(myFakeExtensionManager);

const logAnalyzer = logAnalyzerFactory(extensionManager.create());
const result = await logAnalyzer.isValidLogFileName('johndoe.sql');

expect(result).toBe(true);
});

/**
* I'm using the tecnique "Extract and override", this technique has several steps:
*
* step 1: create a virtual function in the unit under test(logAnalyzer.js in this case)
* that returns the real extension manager, the one that works with the filesystem
*
* step 2: create a class that extends of it
*
* step3: use this new class to create the tests!! :)
*/
it('return false using testableLogAnalyzer', async () => {
const expected = false;
myFakeExtensionManager.willBeValid(expected);

const logAnalyzer = new TestableLogAnalyzerClass(
myFakeExtensionManager
);
const result = await logAnalyzer.isValidLogFileName('johndoe.ts');

expect(result).toBe(expected);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const fakeExtensionManagerFactory = require('./fakeExtensionManager');
const logAnalizer = require('./logAnalyzer.class');

class TestableLogAnalyzer extends logAnalizer {
constructor(extensionManager) {
super();
this.manager = extensionManager;
}
getManager() {
return this.manager;
}
}

module.exports = TestableLogAnalyzer;

0 comments on commit 36ad452

Please sign in to comment.