timeAsyncMethodTests.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. * decaffeinate suggestions:
  3. * DS102: Remove unnecessary code created because of implicit returns
  4. * DS207: Consider shorter variations of null checks
  5. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  6. */
  7. const chai = require('chai')
  8. const { expect } = chai
  9. const path = require('path')
  10. const modulePath = path.join(__dirname, '../../../timeAsyncMethod.js')
  11. const SandboxedModule = require('sandboxed-module')
  12. const sinon = require('sinon')
  13. describe('timeAsyncMethod', function() {
  14. beforeEach(function() {
  15. this.Timer = { done: sinon.stub() }
  16. this.TimerConstructor = sinon.stub().returns(this.Timer)
  17. this.metrics = {
  18. Timer: this.TimerConstructor,
  19. inc: sinon.stub()
  20. }
  21. this.timeAsyncMethod = SandboxedModule.require(modulePath, {
  22. requires: {
  23. './index': this.metrics
  24. }
  25. })
  26. return (this.testObject = {
  27. nextNumber(n, callback) {
  28. return setTimeout(() => callback(null, n + 1), 100)
  29. }
  30. })
  31. })
  32. it('should have the testObject behave correctly before wrapping', function(done) {
  33. return this.testObject.nextNumber(2, (err, result) => {
  34. expect(err).to.not.exist
  35. expect(result).to.equal(3)
  36. return done()
  37. })
  38. })
  39. it('should wrap method without error', function(done) {
  40. this.timeAsyncMethod(
  41. this.testObject,
  42. 'nextNumber',
  43. 'someContext.TestObject'
  44. )
  45. return done()
  46. })
  47. it('should transparently wrap method invocation in timer', function(done) {
  48. this.timeAsyncMethod(
  49. this.testObject,
  50. 'nextNumber',
  51. 'someContext.TestObject'
  52. )
  53. return this.testObject.nextNumber(2, (err, result) => {
  54. expect(err).to.not.exist
  55. expect(result).to.equal(3)
  56. expect(this.TimerConstructor.callCount).to.equal(1)
  57. expect(this.Timer.done.callCount).to.equal(1)
  58. return done()
  59. })
  60. })
  61. it('should increment success count', function(done) {
  62. this.metrics.inc = sinon.stub()
  63. this.timeAsyncMethod(
  64. this.testObject,
  65. 'nextNumber',
  66. 'someContext.TestObject'
  67. )
  68. return this.testObject.nextNumber(2, (err, result) => {
  69. if (err) {
  70. return done(err)
  71. }
  72. expect(this.metrics.inc.callCount).to.equal(1)
  73. expect(
  74. this.metrics.inc.calledWith('someContext_result', 1, {
  75. method: 'TestObject_nextNumber',
  76. status: 'success'
  77. })
  78. ).to.equal(true)
  79. return done()
  80. })
  81. })
  82. describe('when base method produces an error', function() {
  83. beforeEach(function() {
  84. this.metrics.inc = sinon.stub()
  85. return (this.testObject.nextNumber = function(n, callback) {
  86. return setTimeout(() => callback(new Error('woops')), 100)
  87. })
  88. })
  89. it('should propagate the error transparently', function(done) {
  90. this.timeAsyncMethod(
  91. this.testObject,
  92. 'nextNumber',
  93. 'someContext.TestObject'
  94. )
  95. return this.testObject.nextNumber(2, (err, result) => {
  96. expect(err).to.exist
  97. expect(err).to.be.instanceof(Error)
  98. expect(result).to.not.exist
  99. return done()
  100. })
  101. })
  102. return it('should increment failure count', function(done) {
  103. this.timeAsyncMethod(
  104. this.testObject,
  105. 'nextNumber',
  106. 'someContext.TestObject'
  107. )
  108. return this.testObject.nextNumber(2, (err, result) => {
  109. expect(err).to.exist
  110. expect(this.metrics.inc.callCount).to.equal(1)
  111. expect(
  112. this.metrics.inc.calledWith('someContext_result', 1, {
  113. method: 'TestObject_nextNumber',
  114. status: 'failed'
  115. })
  116. ).to.equal(true)
  117. return done()
  118. })
  119. })
  120. })
  121. describe('when a logger is supplied', function() {
  122. beforeEach(function() {
  123. return (this.logger = { log: sinon.stub() })
  124. })
  125. return it('should also call logger.log', function(done) {
  126. this.timeAsyncMethod(
  127. this.testObject,
  128. 'nextNumber',
  129. 'someContext.TestObject',
  130. this.logger
  131. )
  132. return this.testObject.nextNumber(2, (err, result) => {
  133. expect(err).to.not.exist
  134. expect(result).to.equal(3)
  135. expect(this.TimerConstructor.callCount).to.equal(1)
  136. expect(this.Timer.done.callCount).to.equal(1)
  137. expect(this.logger.log.callCount).to.equal(1)
  138. return done()
  139. })
  140. })
  141. })
  142. describe('when the wrapper cannot be applied', function() {
  143. beforeEach(function() {})
  144. return it('should raise an error', function() {
  145. const badWrap = () => {
  146. return this.timeAsyncMethod(
  147. this.testObject,
  148. 'DEFINITELY_NOT_A_REAL_METHOD',
  149. 'someContext.TestObject'
  150. )
  151. }
  152. return expect(badWrap).to.throw(
  153. /^.*expected object property 'DEFINITELY_NOT_A_REAL_METHOD' to be a function.*$/
  154. )
  155. })
  156. })
  157. return describe('when the wrapped function is not using a callback', function() {
  158. beforeEach(function() {
  159. this.realMethod = sinon.stub().returns(42)
  160. return (this.testObject.nextNumber = this.realMethod)
  161. })
  162. it('should not throw an error', function() {
  163. this.timeAsyncMethod(
  164. this.testObject,
  165. 'nextNumber',
  166. 'someContext.TestObject'
  167. )
  168. const badCall = () => {
  169. return this.testObject.nextNumber(2)
  170. }
  171. return expect(badCall).to.not.throw(Error)
  172. })
  173. return it('should call the underlying method', function() {
  174. this.timeAsyncMethod(
  175. this.testObject,
  176. 'nextNumber',
  177. 'someContext.TestObject'
  178. )
  179. const result = this.testObject.nextNumber(12)
  180. expect(this.realMethod.callCount).to.equal(1)
  181. expect(this.realMethod.calledWith(12)).to.equal(true)
  182. return expect(result).to.equal(42)
  183. })
  184. })
  185. })