Skip to content

Commit

Permalink
Merge pull request #164 from Kashoo/issue-163-static-simple-mock
Browse files Browse the repository at this point in the history
Issue 163: Include simple-mock as a static dependency for test-helper
  • Loading branch information
dkichler authored Jan 4, 2018
2 parents d628d1f + ab8f62a commit 882ca8e
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 15 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ language: node_js
node_js:
- "node"
- "lts/*"
- "0.10"
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Changed
- [#156](https://github.com/Kashoo/synctos/issues/156): Fix for an edge case where users assigned a replace role may gain the privilege of removing a document erroneously under certain document definition conditions
- [#157](https://github.com/Kashoo/synctos/issues/157): Swap in Chai as the assertion library used in specs throughout the project
- [#163](https://github.com/Kashoo/synctos/issues/163): Embed simple-mock as a static development dependency

### Fixed
- [#156](https://github.com/Kashoo/synctos/issues/156): Users with a replace role may erroneously gain the privilege of removing a document under certain conditions
- [#160](https://github.com/Kashoo/synctos/issues/160): Unable to import document if it was deleted via Couchbase SDK

## [1.9.3] - 2017-10-23
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,24 +660,25 @@ The synctos project includes a variety of specifications/test cases to verify th

The post [Testing your Sync Gateway functions with synctos](https://blog.couchbase.com/testing-sync-gateway-functions-synctos/) on the official Couchbase blog provides a detailed walkthrough, with examples, for setting up and running tests. The following section also provides a brief overview of the process.

To include the test helper module in your own sync function test cases, you must first ensure that your project [includes](https://docs.npmjs.com/getting-started/using-a-package.json) the development dependencies it relies upon. Update your project's `devDependencies` to include the following packages:
The synctos project uses the [mocha](https://mochajs.org/) test framework for writing and executing test cases, and the following instructions assume that you will too. Similarly, the [Chai](http://chaijs.com/) assertion library is used by synctos. But, by no means are your projects restricted to using either of these libraries for their own tests.

* [simple-mock](https://www.npmjs.com/package/simple-mock) for mocking/stubbing the built-in Sync Gateway functions `requireAccess`, `channel`, `access`, etc.
* [mocha](https://mochajs.org/) or another JavaScript test runner/framework that supports `expect.js`
Some other test runners/frameworks that might be of interest:

The synctos project uses `mocha` for writing and executing test cases and the following instructions assume that you will too, but you are free to substitute something else if you like.
* [Jasmine](https://jasmine.github.io/)
* [Vows](http://vowsjs.org/)

Similarly, the `Chai` assertion library is used in test cases on the synctos project, with the exception of several assertions made in `etc/test-helper.js`, which uses the Node.js assert API. This by no means ties a client project to either of these dependencies and any assertion library may be used for writing test cases in a dependent project, including:
And some alternate assertion libraries:

* [Chai](http://chaijs.com/)
* [Node.js Assertions](https://nodejs.org/dist/latest/docs/api/assert.html)
* [expect.js](https://www.npmjs.com/package/expect.js)

Once your dev dependencies have been set up, run `npm install` to download the extra dependencies.
Once your testing libraries have been set up as development dependencies in your project's [package.json](https://docs.npmjs.com/getting-started/using-a-package.json), run `npm install` to download them.

After that, create a new spec file in your project's `test/` directory (e.g. `test/foobar-spec.js`) and import the test helper module into the empty spec:
After that, create a new specification file in your project's `test/` directory (e.g. `test/foobar-spec.js`) and import the test helper module into the empty spec:

var testHelper = require('../node_modules/synctos/etc/test-helper.js');
```
var testHelper = require('../node_modules/synctos/etc/test-helper.js');
```

Create a new `describe` block to encapsulate the forthcoming test cases and also initialize the synctos test helper before each test case using the `beforeEach` function. For example:

Expand Down
3 changes: 2 additions & 1 deletion etc/sync-function-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ function synctos(doc, oldDoc) {
// is enabled and the document was deleted via the Couchbase SDK. Skip everything else and simply assign the
// public channel
// (https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/channels/index.html#special-channels)
// to the document so that it can be replaced in the future.
// to the document so that users will get a 404 Not Found if they attempt to fetch (i.e. "view") the deleted
// document rather than a 403 Forbidden.
requireAccess('!');
channel('!');

Expand Down
2 changes: 1 addition & 1 deletion etc/test-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ exports.verifyAccessDenied = verifyAccessDenied;
exports.verifyUnknownDocumentType = verifyUnknownDocumentType;

var assert = require('assert');
var simple = require('simple-mock');
var simple = require('../lib/simple-mock/index.js');
var fs = require('fs');
var syncFunctionLoader = require('./sync-function-loader.js');

Expand Down
22 changes: 22 additions & 0 deletions lib/simple-mock/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2014 Pieter Raubenheimer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1 change: 1 addition & 0 deletions lib/simple-mock/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.8.0
240 changes: 240 additions & 0 deletions lib/simple-mock/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/* global define */
(function (global, module) { // Browser compatibility
var simple = module.exports
var mocks = []
var totalCalls = 0

/**
* Restore the current simple and create a new one
*
* @param {Object} [obj]
* @param {String} [key]
* @api public
*/
simple.restore = function (obj, key) {
if (obj && key) {
mocks.some(function (mock, i) {
if (mock.obj !== obj || mock.key !== key) return

mock.restore()
mocks.splice(i, 1)
return true
})
return
}

mocks.forEach(_restoreMock)
mocks = []
}

/**
* Create a mocked value on an object
*
* @param {Object} obj
* @param {String} key
* @param {Function|Value} mockValue
* @return {Function} mock
* @api public
*/
simple.mock = function (obj, key, mockValue) {
if (!arguments.length) {
return simple.spyOrStub()
} else if (arguments.length === 1) {
return simple.spyOrStub(obj)
} else if (isFunction(mockValue)) {
mockValue = simple.spyOrStub(mockValue)
} else if (arguments.length === 2) {
mockValue = simple.spyOrStub(obj, key)
}

var mock = {
obj: obj,
key: key,
mockValue: mockValue,
oldValue: obj[key],
oldValueHasKey: (key in obj)
}

mock.restore = _restoreMock.bind(null, mock)
mocks.unshift(mock)

obj[key] = mockValue
return mockValue
}

/**
* Create a stub function
*
* @param {Function|Object} wrappedFn
* @param {String} [key]
* @return {Function} spy
* @api public
*/
simple.spyOrStub = simple.stub = simple.spy = function (wrappedFn, key) {
if (arguments.length === 2) {
wrappedFn = wrappedFn[key]
}

var originalFn = wrappedFn

var stubFn = function () {
var action = (newFn.loop) ? newFn.actions[(newFn.callCount - 1) % newFn.actions.length] : newFn.actions.shift()

if (!action) return
if (action.throwError) throw action.throwError
if ('returnValue' in action) return action.returnValue
if ('fn' in action) return action.fn.apply(action.ctx || this, arguments)

var cb = ('cbArgIndex' in action) ? arguments[action.cbArgIndex] : arguments[arguments.length - 1]
if (action.cbArgs) return cb.apply(action.ctx || null, action.cbArgs)
}

var newFn = function () {
var call = {
k: totalCalls++, // Keep count of calls to record the absolute order of calls
args: Array.prototype.slice.call(arguments, 0),
arg: arguments[0],
context: this
}
newFn.calls.push(call)
newFn.firstCall = newFn.firstCall || call
newFn.lastCall = call
newFn.callCount++
newFn.called = true

try {
call.returned = (wrappedFn || _noop).apply(this, arguments)
} catch(e) {
call.threw = e
throw e
}
return call.returned
}

// Spy
newFn.reset = function () {
newFn.calls = []
newFn.lastCall = { args: [] } // For dot-safety
newFn.callCount = 0
newFn.called = false
}
newFn.reset()

// Stub
newFn.actions = []
newFn.loop = true

newFn.callbackWith = newFn.callback = function () {
wrappedFn = stubFn
newFn.actions.push({ cbArgs: arguments })
return newFn // Chainable
}

newFn.callbackArgWith = newFn.callbackAtIndex = function () {
wrappedFn = stubFn
newFn.actions.push({
cbArgs: Array.prototype.slice.call(arguments, 1),
cbArgIndex: arguments[0]
})
return newFn // Chainable
}

newFn.inThisContext = function (obj) {
var action = newFn.actions[newFn.actions.length - 1]
action.ctx = obj
return newFn // Chainable
}

newFn.withArgs = function () {
var action = newFn.actions[newFn.actions.length - 1]
action.cbArgs = arguments
return newFn // Chainable
}

newFn.returnWith = function (returnValue) {
wrappedFn = stubFn
newFn.actions.push({ returnValue: returnValue })
return newFn // Chainable
}

newFn.throwWith = function (err) {
wrappedFn = stubFn
newFn.actions.push({ throwError: err })
return newFn // Chainable
}

newFn.resolveWith = function (value) {
if (simple.Promise.when) return newFn.callFn(function createResolvedPromise () { return simple.Promise.when(value) })
return newFn.callFn(function createResolvedPromise () { return simple.Promise.resolve(value) })
}

newFn.rejectWith = function (value) {
return newFn.callFn(function createRejectedPromise () { return simple.Promise.reject(value) })
}

newFn.callFn = function (fn) {
wrappedFn = stubFn
newFn.actions.push({ fn: fn })
return newFn // Chainable
}

newFn.withActions = function (actions) {
wrappedFn = stubFn
if (actions && actions.length >= 0) {
Array.prototype.push.apply(newFn.actions, actions)
}
return newFn // Chainable
}

newFn.callOriginal = newFn.callOriginalFn = function () {
wrappedFn = stubFn
newFn.actions.push({ fn: originalFn })
return newFn // Chainable
}

newFn.withLoop = function () {
newFn.loop = true
return newFn // Chainable
}

newFn.noLoop = function () {
newFn.loop = false
return newFn // Chainable
}
return newFn
}

function _restoreMock (mock) {
if (!mock.oldValueHasKey) {
delete mock.obj[mock.key]
return
}
mock.obj[mock.key] = mock.oldValue
}

function _noop () {}

/**
* Return whether the passed variable is a function
*
* @param {Mixed} value
* @return {Boolean}
* @api private
*/
function isFunction (value) {
return Object.prototype.toString.call(value) === funcClass
}
var funcClass = '[object Function]'

// Browser compatibility
if (typeof define === 'function' && define.amd) {
define(function () {
return simple
})
} else if (typeof window !== 'undefined') {
window.simple = simple
}

// Native Promise support
if (typeof Promise !== 'undefined') simple.Promise = Promise
})(this, typeof module !== 'undefined' ? module : {exports: {}})
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"devDependencies": {
"chai": "^4.1.2",
"jshint": "^2.9.4",
"mocha": "^3.3.0",
"simple-mock": "^0.7.3"
"mocha": "^3.3.0"
},
"scripts": {
"test": "etc/prepare-tests.sh && node_modules/.bin/mocha",
Expand Down

0 comments on commit 882ca8e

Please sign in to comment.