Skip to content

Commit 36ad452

Browse files
committed
chapter 04: copy code from chapter 03 to the chapter 04 folder in order to continue with the book
1 parent 9f412b1 commit 36ad452

File tree

10 files changed

+325
-0
lines changed

10 files changed

+325
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ArgumentError extends Error {}
2+
3+
module.exports = ArgumentError;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function alwaysValidFakeExtensionManager() {
2+
/**
3+
* @param {string} fileName
4+
* @return {Promise}
5+
*/
6+
async function isValid(fileName) {
7+
return true;
8+
}
9+
10+
return {
11+
isValid,
12+
};
13+
}
14+
15+
module.exports = alwaysValidFakeExtensionManager;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const fileExtensionManagerFactory = require('./fileExtensionManager');
2+
3+
function extensionManager() {
4+
let customManager = null;
5+
6+
function create() {
7+
if (customManager !== null) {
8+
return customManager;
9+
}
10+
11+
return fileExtensionManagerFactory();
12+
}
13+
14+
function setManager(manager) {
15+
customManager = manager;
16+
}
17+
18+
return {
19+
create,
20+
setManager,
21+
};
22+
}
23+
24+
module.exports = extensionManager;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function fakeExtensionManager() {
2+
/**
3+
* @type {boolean}
4+
*/
5+
let valid = false;
6+
7+
/**
8+
* @param {boolean} value
9+
*/
10+
function willBeValid(value) {
11+
valid = value;
12+
}
13+
14+
/**
15+
* @param {string} fileName
16+
*/
17+
async function isValid(fileName) {
18+
return valid;
19+
}
20+
21+
return {
22+
willBeValid,
23+
isValid,
24+
};
25+
}
26+
module.exports = fakeExtensionManager;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const fs = require('fs');
2+
const util = require('util');
3+
4+
const readFilePromisied = util.promisify(fs.readFile);
5+
6+
function fileExtensionManager() {
7+
/**
8+
* @param {string} fileName
9+
* @return {Promise}
10+
*/
11+
async function isValid(fileName) {
12+
const fileNameExtensions = await readFilePromisied(
13+
`${__dirname}/fileNameExtensions.config.json`,
14+
'utf8'
15+
).then(fileContent => JSON.parse(fileContent).extensions);
16+
17+
const isValidExtension = fileNameExtensions.some(
18+
function checkFileNameExtension(extension) {
19+
if (
20+
fileName
21+
.toUpperCase()
22+
.endsWith(`.${extension.toUpperCase()}`)
23+
) {
24+
return true;
25+
}
26+
27+
return false;
28+
}
29+
);
30+
31+
return isValidExtension;
32+
}
33+
34+
return {
35+
isValid,
36+
};
37+
}
38+
39+
module.exports = fileExtensionManager;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extensions": ["slf", "sql"]
3+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const ArgumentError = require('./ArgumentError');
2+
const fileExtensionManagerFactory = require('./fileExtensionManager');
3+
4+
class LogAnalyzer {
5+
constructor() {
6+
/**
7+
* @type {boolean}
8+
*/
9+
this.wasLastFileNameValid = false;
10+
}
11+
/**
12+
* @return {boolean}
13+
*/
14+
getWasLastFileNameValid() {
15+
return this.wasLastFileNameValid;
16+
}
17+
18+
/**
19+
* a virtual function created to use "Extract and Override" technique
20+
*/
21+
getManager() {
22+
return fileExtensionManagerFactory();
23+
}
24+
25+
/**
26+
* @param {string} fileName
27+
* @return {Promise}
28+
*/
29+
async isValidLogFileName(fileName) {
30+
this.wasLastFileNameValid = false;
31+
32+
if (fileName === '') {
33+
throw new ArgumentError('filename has to be provided');
34+
}
35+
36+
const result = await this.getManager().isValid(fileName);
37+
38+
if (!result) {
39+
return false;
40+
}
41+
42+
this.wasLastFileNameValid = true;
43+
return true;
44+
}
45+
}
46+
47+
module.exports = LogAnalyzer;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const ArgumentError = require('./ArgumentError');
2+
3+
function logAnalyzer(extensionManager) {
4+
/**
5+
* @type {boolean}
6+
*/
7+
let wasLastFileNameValid;
8+
9+
/**
10+
* @return {boolean}
11+
*/
12+
function getWasLastFileNameValid() {
13+
return wasLastFileNameValid;
14+
}
15+
16+
/**
17+
* @param {string} fileName
18+
* @return {Promise}
19+
*/
20+
async function isValidLogFileName(fileName) {
21+
wasLastFileNameValid = false;
22+
23+
if (fileName === '') {
24+
throw new ArgumentError('filename has to be provided');
25+
}
26+
27+
const result = await extensionManager.isValid(fileName);
28+
29+
if (!result) {
30+
return false;
31+
}
32+
33+
wasLastFileNameValid = true;
34+
return true;
35+
}
36+
37+
return {
38+
getWasLastFileNameValid,
39+
isValidLogFileName,
40+
};
41+
}
42+
43+
module.exports = logAnalyzer;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const logAnalyzerFactory = require('./logAnalyzer');
2+
const fakeExtensionManagerFactory = require('./fakeExtensionManager');
3+
const extensionManagerFactory = require('./extensionManager');
4+
5+
// imported to try the technique "Extract and override"
6+
const TestableLogAnalyzerClass = require('./testableLogAnalyzer.class');
7+
8+
let myFakeExtensionManager;
9+
10+
beforeEach(() => {
11+
myFakeExtensionManager = fakeExtensionManagerFactory();
12+
});
13+
14+
describe.each([
15+
['johndoe.js', false],
16+
['johndoe.slf', true],
17+
['johndoe.SLF', true],
18+
])('isValidLogFileName("%s"))', (fileName, expected) => {
19+
it(`bad extension returns ${expected}`, async () => {
20+
myFakeExtensionManager.willBeValid(expected);
21+
22+
const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);
23+
const result = await logAnalyzer.isValidLogFileName(fileName);
24+
expect(result).toBe(expected);
25+
});
26+
});
27+
28+
describe('isValidLogFileName', () => {
29+
it('empty filename throws error', async () => {
30+
async function emptyLogFileName() {
31+
const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);
32+
33+
return logAnalyzer.isValidLogFileName('');
34+
}
35+
36+
await expect(emptyLogFileName()).rejects.toThrow(
37+
'filename has to be provided'
38+
);
39+
});
40+
41+
/**
42+
* an example of state-based testing
43+
*/
44+
it.each`
45+
fileName | expected
46+
${'johndoe.foo'} | ${false}
47+
${'johndoe.slf'} | ${true}
48+
`(
49+
'when called there changes wasLastFileNameValid that returns $expected',
50+
async ({ fileName, expected }) => {
51+
myFakeExtensionManager.willBeValid(expected);
52+
53+
const logAnalyzer = logAnalyzerFactory(myFakeExtensionManager);
54+
await logAnalyzer.isValidLogFileName(fileName);
55+
const result = logAnalyzer.getWasLastFileNameValid();
56+
57+
expect(result).toBe(expected);
58+
}
59+
);
60+
61+
/**
62+
* an example of use of "injecting a fake just before a method call"
63+
* right now I'm not injecting a fake, and extensionManager is returning fileExtensionManager,
64+
* therefore this test is an integration test and not a unit test!!!!
65+
*/
66+
it('return true using extensionManagerFactory', async () => {
67+
const extensionManager = extensionManagerFactory();
68+
const logAnalyzer = logAnalyzerFactory(extensionManager.create());
69+
const result = await logAnalyzer.isValidLogFileName('johndoe.sql');
70+
71+
expect(result).toBe(true);
72+
});
73+
74+
/**
75+
* an example of use of "injecting a fake just before a method call"
76+
* In this case I'm setting a fake extension manager, that converts this in a unit test!!, because
77+
* right now I have not external dependencies.
78+
*/
79+
it('return true using extensionManagerFactory', async () => {
80+
myFakeExtensionManager.willBeValid(true);
81+
const extensionManager = extensionManagerFactory();
82+
extensionManager.setManager(myFakeExtensionManager);
83+
84+
const logAnalyzer = logAnalyzerFactory(extensionManager.create());
85+
const result = await logAnalyzer.isValidLogFileName('johndoe.sql');
86+
87+
expect(result).toBe(true);
88+
});
89+
90+
/**
91+
* I'm using the tecnique "Extract and override", this technique has several steps:
92+
*
93+
* step 1: create a virtual function in the unit under test(logAnalyzer.js in this case)
94+
* that returns the real extension manager, the one that works with the filesystem
95+
*
96+
* step 2: create a class that extends of it
97+
*
98+
* step3: use this new class to create the tests!! :)
99+
*/
100+
it('return false using testableLogAnalyzer', async () => {
101+
const expected = false;
102+
myFakeExtensionManager.willBeValid(expected);
103+
104+
const logAnalyzer = new TestableLogAnalyzerClass(
105+
myFakeExtensionManager
106+
);
107+
const result = await logAnalyzer.isValidLogFileName('johndoe.ts');
108+
109+
expect(result).toBe(expected);
110+
});
111+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const fakeExtensionManagerFactory = require('./fakeExtensionManager');
2+
const logAnalizer = require('./logAnalyzer.class');
3+
4+
class TestableLogAnalyzer extends logAnalizer {
5+
constructor(extensionManager) {
6+
super();
7+
this.manager = extensionManager;
8+
}
9+
getManager() {
10+
return this.manager;
11+
}
12+
}
13+
14+
module.exports = TestableLogAnalyzer;

0 commit comments

Comments
 (0)