assertion.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. var AssertionError = require('./assertion-error');
  2. var util = require('./util');
  3. /**
  4. * should Assertion
  5. * @param {*} obj Given object for assertion
  6. * @constructor
  7. * @memberOf should
  8. * @static
  9. */
  10. function Assertion(obj) {
  11. this.obj = obj;
  12. this.anyOne = false;
  13. this.negate = false;
  14. this.params = {actual: obj};
  15. }
  16. /**
  17. * Way to extend Assertion function. It uses some logic
  18. * to define only positive assertions and itself rule with negative assertion.
  19. *
  20. * All actions happen in subcontext and this method take care about negation.
  21. * Potentially we can add some more modifiers that does not depends from state of assertion.
  22. * @memberOf Assertion
  23. * @category assertion
  24. * @static
  25. * @param {String} name Name of assertion. It will be used for defining method or getter on Assertion.prototype
  26. * @param {Function} func Function that will be called on executing assertion
  27. * @example
  28. *
  29. * Assertion.add('asset', function() {
  30. * this.params = { operator: 'to be asset' };
  31. *
  32. * this.obj.should.have.property('id').which.is.a.Number();
  33. * this.obj.should.have.property('path');
  34. * });
  35. */
  36. Assertion.add = function(name, func) {
  37. var prop = {enumerable: true, configurable: true};
  38. prop.value = function() {
  39. var context = new Assertion(this.obj, this, name);
  40. context.anyOne = this.anyOne;
  41. try {
  42. func.apply(context, arguments);
  43. } catch(e) {
  44. //check for fail
  45. if(e instanceof AssertionError) {
  46. //negative fail
  47. if(this.negate) {
  48. this.obj = context.obj;
  49. this.negate = false;
  50. return this;
  51. }
  52. if(context !== e.assertion) {
  53. context.params.previous = e;
  54. }
  55. //positive fail
  56. context.negate = false;
  57. context.fail();
  58. }
  59. // throw if it is another exception
  60. throw e;
  61. }
  62. //negative pass
  63. if(this.negate) {
  64. context.negate = true;//because .fail will set negate
  65. context.params.details = 'false negative fail';
  66. context.fail();
  67. }
  68. //positive pass
  69. if(!this.params.operator) this.params = context.params;//shortcut
  70. this.obj = context.obj;
  71. this.negate = false;
  72. return this;
  73. };
  74. Object.defineProperty(Assertion.prototype, name, prop);
  75. };
  76. Assertion.addChain = function(name, onCall) {
  77. onCall = onCall || function() {
  78. };
  79. Object.defineProperty(Assertion.prototype, name, {
  80. get: function() {
  81. onCall();
  82. return this;
  83. },
  84. enumerable: true
  85. });
  86. };
  87. /**
  88. * Create alias for some `Assertion` property
  89. *
  90. * @memberOf Assertion
  91. * @category assertion
  92. * @static
  93. * @param {String} from Name of to map
  94. * @param {String} to Name of alias
  95. * @example
  96. *
  97. * Assertion.alias('true', 'True');
  98. */
  99. Assertion.alias = function(from, to) {
  100. var desc = Object.getOwnPropertyDescriptor(Assertion.prototype, from);
  101. if(!desc) throw new Error('Alias ' + from + ' -> ' + to + ' could not be created as ' + from + ' not defined');
  102. Object.defineProperty(Assertion.prototype, to, desc);
  103. };
  104. Assertion.prototype = {
  105. constructor: Assertion,
  106. /**
  107. * Base method for assertions. Before calling this method need to fill Assertion#params object. This method usually called from other assertion methods.
  108. * `Assertion#params` can contain such properties:
  109. * * `operator` - required string containing description of this assertion
  110. * * `obj` - optional replacement for this.obj, it usefull if you prepare more clear object then given
  111. * * `message` - if this property filled with string any others will be ignored and this one used as assertion message
  112. * * `expected` - any object used when you need to assert relation between given object and expected. Like given == expected (== is a relation)
  113. * * `details` - additional string with details to generated message
  114. *
  115. * @memberOf Assertion
  116. * @category assertion
  117. * @param {*} expr Any expression that will be used as a condition for asserting.
  118. * @example
  119. *
  120. * var a = new should.Assertion(42);
  121. *
  122. * a.params = {
  123. * operator: 'to be magic number',
  124. * }
  125. *
  126. * a.assert(false);
  127. * //throws AssertionError: expected 42 to be magic number
  128. */
  129. assert: function(expr) {
  130. if(expr) return this;
  131. var params = this.params;
  132. if('obj' in params && !('actual' in params)) {
  133. params.actual = params.obj;
  134. } else if(!('obj' in params) && !('actual' in params)) {
  135. params.actual = this.obj;
  136. }
  137. params.stackStartFunction = params.stackStartFunction || this.assert;
  138. params.negate = this.negate;
  139. params.assertion = this;
  140. throw new AssertionError(params);
  141. },
  142. /**
  143. * Shortcut for `Assertion#assert(false)`.
  144. *
  145. * @memberOf Assertion
  146. * @category assertion
  147. * @example
  148. *
  149. * var a = new should.Assertion(42);
  150. *
  151. * a.params = {
  152. * operator: 'to be magic number',
  153. * }
  154. *
  155. * a.fail();
  156. * //throws AssertionError: expected 42 to be magic number
  157. */
  158. fail: function() {
  159. return this.assert(false);
  160. },
  161. /**
  162. * Negation modifier. Current assertion chain become negated. Each call invert negation on current assertion.
  163. *
  164. * @memberOf Assertion
  165. * @category assertion
  166. */
  167. get not() {
  168. this.negate = !this.negate;
  169. return this;
  170. },
  171. /**
  172. * Any modifier - it affect on execution of sequenced assertion to do not `check all`, but `check any of`.
  173. *
  174. * @memberOf Assertion
  175. * @category assertion
  176. */
  177. get any() {
  178. this.anyOne = true;
  179. return this;
  180. }
  181. };
  182. module.exports = Assertion;