Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit 755f4d9b5f865a82fb40e6f30af056d82506f20c 1 parent 95b0a9e
@eldargab authored
View
2  README.md
@@ -1,4 +1,4 @@
-node-fake-fs
+fake-fs
============
Fake node.js file system for testing
View
1  index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/fake-fs')
View
106 lib/fake-fs.js
@@ -0,0 +1,106 @@
+var stat = require('./stat')
+var normalize = require('path').normalize
+
+function FsError (code) {
+ var err = new Error(code)
+ err.code = code
+ return err
+}
+
+
+module.exports = Fs
+
+function Fs (paths) {
+ this.root = new stat.Dir
+}
+
+Fs.prototype.dir = function (path, opts) {
+ return this._add(path, new stat.Dir(opts))
+}
+
+Fs.prototype.file = function (path, content, encoding) {
+ return this._add(path, new stat.File(content, encoding))
+}
+
+Fs.prototype._add = function (path, item) {
+ var segs = path.split('/')
+ var dir = this.root
+ for (var i = 0; i < segs.length - 1; i++) {
+ dir = dir.childs[segs[i]] || (dir.childs[segs[i]] = new stat.Dir)
+ if (!dir.isDirectory()) {
+ throw new Error('There is already ' + dir + ' defined at ' + segs.slice(i).join('/'))
+ }
+ }
+ dir.childs[segs[i]] = item
+ return this
+}
+
+Fs.prototype._itemAt = function (path) {
+ path = normalize(path).replace('\\', '/') // windows support
+ var segs = path.split('/')
+ var item = this.root
+ if (segs[0] == '.') return item
+ for (var i = 0; i < segs.length; i++) {
+ item = item.childs && item.childs[segs[i]]
+ if (!item) return
+ }
+ return item
+}
+
+Fs.prototype._get = function (path) {
+ var item = this._itemAt(path)
+ if (!item) throw FsError('ENOENT')
+ return item
+}
+
+
+Fs.prototype.statSync = function (path) {
+ return this._get(path)
+}
+
+Fs.prototype.existsSync = function (path) {
+ return !!this._itemAt(path)
+}
+
+Fs.prototype.readdirSync = function (dir) {
+ var item = this._get(dir)
+ if (!item.isDirectory()) throw FsError('ENOTDIR')
+ return Object.keys(item.childs)
+}
+
+Fs.prototype.readFileSync = function (filename, encoding) {
+ var item = this._get(filename)
+ if (item.isDirectory()) throw FsError('EISDIR')
+ return item.read(encoding)
+}
+
+;['readdir', 'stat'].forEach(function (meth) {
+ var sync = meth + 'Sync'
+ Fs.prototype[meth] = function (p, cb) {
+ var res, err
+ try {
+ res = this[sync].call(this, p)
+ } catch (e) {
+ err = e
+ }
+ cb && cb(err, res)
+ }
+})
+
+Fs.prototype.readFile = function (filename, encoding, cb) {
+ if (typeof encoding != 'string') {
+ cb = encoding
+ encoding = undefined
+ }
+ var res, err
+ try {
+ res = this.readFileSync(filename, encoding)
+ } catch (e) {
+ err = e
+ }
+ cb && cb(err, res)
+}
+
+Fs.prototype.exists = function (path, cb) {
+ cb && cb(this.existsSync(path))
+}
View
68 lib/stat.js
@@ -0,0 +1,68 @@
+function mix (target, src) {
+ for (var key in src) {
+ target[key] = src[key]
+ }
+}
+
+function stat (Klass, props) {
+ Klass.prototype = {
+ isDirectory: function () {
+ return false
+ },
+ isFile: function () {
+ return false
+ }
+ }
+ mix(Klass.prototype, props)
+ return Klass
+}
+
+
+exports.Dir = stat(function Dir (opts) {
+ mix(this, opts)
+ this.childs = {}
+}, {
+ isDirectory: function () {
+ return true
+ },
+
+ toString: function () {
+ return 'directory'
+ }
+})
+
+
+exports.File = stat(function File (content, encoding) {
+ if (typeof content == 'string') {
+ this.content = content
+ this.encoding = encoding
+ } else if (Buffer.isBuffer(content)) {
+ this.content = content
+ } else {
+ mix(this, content)
+ }
+ if (this.content != null &&
+ typeof this.content != 'string' &&
+ !Buffer.isBuffer(this.content)) {
+ throw new Error('File content can be a string or buffer')
+ }
+}, {
+ isFile: function () {
+ return true
+ },
+
+ read: function (encoding) {
+ return encoding
+ ? this._buffer().toString(encoding)
+ : this._buffer()
+ },
+
+ _buffer: function () {
+ if (Buffer.isBuffer(this.content)) return this.content
+ return this.content = new Buffer(this.content || '', this.encoding || 'utf8')
+ },
+
+ toString: function () {
+ return 'file'
+ }
+})
View
24 package.json
@@ -0,0 +1,24 @@
+{
+ "name": "fake-fs",
+ "version": "0.0.0",
+ "description": "Fake file system for testing",
+ "scripts": {
+ "test": "mocha -R spec"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/eldargab/node-fake-fs.git"
+ },
+ "keywords": [
+ "fs",
+ "fake",
+ "mocks",
+ "test"
+ ],
+ "devDependecies": {
+ "mocha": "1.4.2",
+ "sinon": "1.4.2"
+ },
+ "author": "Eldar Gabdullin <eldargab@gmail.com>",
+ "license": "MIT"
+}
View
155 test/fake-fs.js
@@ -0,0 +1,155 @@
+var should = require('should')
+var sinon = require('sinon')
+var Fs = require('..')
+
+function Cb () {
+ var spy = sinon.spy()
+
+ spy.error = function (err) {
+ this.calledOnce.should.be.true
+ this.firstCall.args[0].code.should.equal(err)
+ }
+
+ spy.result = function () {
+ this.calledOnce.should.be.true
+ should.not.exist(this.firstCall.args[0])
+ return this.firstCall.args[1]
+ }
+
+ return spy
+}
+
+describe('Fake FS', function () {
+ var fs, cb
+
+ beforeEach(function () {
+ fs = new Fs
+ cb = Cb()
+ })
+
+ describe('.dir(path, [opts])', function () {
+ it('Should define dir', function () {
+ fs.dir('a/b/c').statSync('a/b/c').isDirectory().should.be.true
+ })
+
+ it('Should support options', function () {
+ var stat = fs.dir('a', {
+ mtime: 10,
+ atime: 30
+ }).statSync('a')
+ stat.should.have.property('mtime').equal(10)
+ stat.should.have.property('atime').equal(30)
+ })
+
+ it('Should work like mkdir -p', function () {
+ fs.dir('a', { mtime: 100 })
+ fs.dir('a/b/c')
+ fs.statSync('a').mtime.should.equal(100)
+ fs.statSync('a/b').isDirectory().should.be.true
+ })
+ })
+
+ describe('.file(path, [opts | content, [encoding]]', function () {
+ it('Should define file', function () {
+ fs.file('a/b.txt').statSync('a/b.txt').isFile().should.be.true
+ })
+
+ it('Should work like mkdir -p for parent dir', function () {
+ fs.dir('a', { mtime: 100 })
+ fs.file('a/b.txt')
+ fs.statSync('a').mtime.should.equal(100)
+ })
+
+ it('Should support content & encoding params', function () {
+ fs.file('hello.txt', 'hello')
+ .readFileSync('hello.txt', 'utf8')
+ .should.equal('hello')
+
+ fs.file('bin', 'TWFu', 'base64')
+ .readFileSync('bin', 'utf8').should.equal('Man')
+
+ fs.file('bin2', new Buffer([10]))
+ fs.readFileSync('bin2')[0].should.equal(10)
+ })
+
+ it('Should support options param', function () {
+ fs.file('hello.txt', {
+ atime: 10,
+ mtime: 20,
+ content: 'a'
+ })
+ var stat = fs.statSync('hello.txt')
+ stat.should.have.property('atime').equal(10)
+ stat.should.have.property('mtime').equal(20)
+ fs.readFileSync('hello.txt')[0].should.equal(97)
+ })
+ })
+
+ describe('.stat()', function () {
+ it('Should return stats', function () {
+ fs.file('a/b/c', {ctime: 123}).stat('a/b/c', cb)
+ cb.result().should.have.property('ctime').equal(123)
+ })
+
+ it('Should throw ENOENT on non-existent path', function () {
+ fs.stat('undefined', cb)
+ cb.error('ENOENT')
+ })
+ })
+
+ describe('.readdir()', function () {
+ it('Should list a dir contents', function () {
+ fs.dir('a').file('b.txt').readdir('.', cb)
+ cb.result().should.eql(['a', 'b.txt'])
+ })
+
+ it('Should throw ENOENT on non-existent path', function () {
+ fs.readdir('a', cb)
+ cb.error('ENOENT')
+ })
+
+ it('Should throw ENOTDIR on non-dir', function () {
+ fs.file('a.txt').readdir('a.txt', cb)
+ cb.error('ENOTDIR')
+ })
+ })
+
+ describe('.exists()', function () {
+ it('Should return true on existent path', function (done) {
+ fs.dir('asd').exists('asd', function (exists) {
+ exists.should.be.true
+ done()
+ })
+ })
+
+ it('Should return false for non-existent path', function (done) {
+ fs.exists('non-existent', function (exists) {
+ exists.should.be.false
+ done()
+ })
+ })
+ })
+
+ describe('.readFile()', function () {
+ it('Should read file contents', function () {
+ var content = new Buffer([1, 2, 3])
+ fs.file('bin', content).readFile('bin', cb)
+ cb.result().should.equal(content)
+ })
+
+ it('Should decode file contents', function () {
+ fs.file('file.txt', new Buffer([97])).readFile('file.txt', 'ascii', cb)
+ cb.result().should.equal('a')
+ })
+
+ it('Should throw ENOENT on non-existent file', function () {
+ fs.readFile('foo', cb)
+ cb.error('ENOENT')
+ })
+
+ it('Should throw EISDIR on directory', function () {
+ fs.dir('dir').readFile('dir', cb)
+ cb.error('EISDIR')
+ })
+ })
+})
Please sign in to comment.
Something went wrong with that request. Please try again.