源码链接:https://github.com/wuyangqin/node-todo-list
使用node的fs文件模块实现了一个简单的用命令行操作的todo list
技术细节
- 使用node.js
fs模块实现文件读写
- 使用
commander.js编写命令行命令
- 使用
inquirer.js 实现用户与命令行的交互
- 使用
jest.js 模拟fs,编写单元测试
效果演示
安装
1
| npm install -g node-todo-xx
|
or
1
| yarn global add node-todo-xx
|
使用
1 2 3
| todo todo add 任务名 todo clear
|
todo 查看任务列表


todo add 添加任务

todo clear 清除任务

实现过程
逻辑梳理
该项目的实现逻辑如图所示:

目录结构
1 2 3 4 5 6 7
| ├── __mocks__ | └── fs.js ├── __test__ | └── db.test.js ├── cli.js ├── db.js └── index.js
|
读写文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const os = require('os'); const path = require('path'); const fs = require('fs');
const homedir = os.homedir(); const home = process.env.HOME || homedir; const dbPath = path.join(home,'.todo')
const db = { read(path = dbPath) { return new Promise((resolve, reject) => { fs.readFile(path,{ flag: 'a+'}, (readError,data) => { if (readError) return reject(readError) let list try { list = JSON.parse(data.toString()) } catch (e) { list = [] } resolve(list) }) }) }, write(list, path = dbPath) { return new Promise((resolve, reject) => { const string = JSON.stringify(list) fs.writeFile(path,string + '\n',(writeError)=>{ if(writeError) return reject(writeError) resolve() }) }) } }
module.exports = db
|
单元测试
测试命令
fs mock
从上面的逻辑梳理图可以看到,这个项目最主要的就是对任务文件进行操作的两个函数,因此将围绕这两个函数进行单元测试的编写。
然而编写单元测试有一条原则就是,测试代码不要与外界进行交互,比如如果我通过测试代码读取用户硬盘上的某个文件,而这个文件路径刚好在用户硬盘中存在,这就很尴尬啦。因此jest提供了对node模块进行mock的功能——相当于对node的模块进行接管,调用我们自己编写的方法。具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
const fs = jest.genMockFromModule('fs') const _fs = jest.requireActual('fs')
Object.assign(fs, _fs)
let readMocks = {} fs.setReadFileMock = (path, error, data) => { readMocks[path] = [error, data] } fs.readFile = (path,options,callback) => { if (callback === undefined) callback = options if (path in readMocks) { callback(...readMocks[path]) } else { _fs.readFile(path, options, callback) } }
let writeMocks = {} fs.setWriteFileMock = (path, fn) => { writeMocks[path] = fn } fs.writeFile = (path, data, options, callback)=>{ if (callback === undefined) callback = options if (path in writeMocks) { writeMocks[path](path, data, options, callback) } else { _fs.writeFile(path, data, options, callback) } }
fs.clearMocks = () => { readMocks = {} writeMocks = {} }
module.exports = fs
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const db = require('../db.js'); const fs = require('fs'); jest.mock('fs')
describe('db', () => { afterEach(()=>{ fs.clearMocks() }) it ('can read', async () => { const data = [{title: 'hi', done: false}] fs.setReadFileMock('/test',null, JSON.stringify(data)) const list = await db.read('/test') expect(list).toStrictEqual(data) }) it ('can write', async () => { let fakeFile fs.setWriteFileMock('/test1', (path, data, callback) => { fakeFile = data callback(null) }) const list = [{title: 'hi', done: false}] await db.write(list,'/test1') expect(fakeFile).toBe(JSON.stringify(list) + '\n') }) })
|