请求取消模块单元测试

请求取消模块是 ts-axios 库核心流程其中一个分支,也是非常重要的模块,我们将从基础库和业务流程模块 2 个方面去编写单元测试。

Cancel 类单元测试

cancel/Cancel.spec.ts

  1. import Cancel, { isCancel } from '../../src/cancel/Cancel'
  2. describe('cancel:Cancel', () => {
  3. test('should returns correct result when message is specified', () => {
  4. const cancel = new Cancel('Operation has been canceled.')
  5. expect(cancel.message).toBe('Operation has been canceled.')
  6. })
  7. test('should returns true if value is a Cancel', () => {
  8. expect(isCancel(new Cancel())).toBeTruthy()
  9. })
  10. test('should returns false if value is not a Cancel', () => {
  11. expect(isCancel({ foo: 'bar' })).toBeFalsy()
  12. })
  13. })

CancelToken 类单元测试

cancel/CancelToken.spec.ts

  1. import CancelToken from '../../src/cancel/CancelToken'
  2. import Cancel from '../../src/cancel/Cancel'
  3. import { Canceler } from '../../src/types'
  4. describe('CancelToken', () => {
  5. describe('reason', () => {
  6. test('should returns a Cancel if cancellation has been requested', () => {
  7. let cancel: Canceler
  8. let token = new CancelToken(c => {
  9. cancel = c
  10. })
  11. cancel!('Operation has been canceled.')
  12. expect(token.reason).toEqual(expect.any(Cancel))
  13. expect(token.reason!.message).toBe('Operation has been canceled.')
  14. })
  15. test('should has no side effect if call cancellation for multi times', () => {
  16. let cancel: Canceler
  17. let token = new CancelToken(c => {
  18. cancel = c
  19. })
  20. cancel!('Operation has been canceled.')
  21. cancel!('Operation has been canceled.')
  22. expect(token.reason).toEqual(expect.any(Cancel))
  23. expect(token.reason!.message).toBe('Operation has been canceled.')
  24. })
  25. test('should returns undefined if cancellation has not been requested', () => {
  26. const token = new CancelToken(() => {
  27. // do nothing
  28. })
  29. expect(token.reason).toBeUndefined()
  30. })
  31. })
  32. describe('promise', () => {
  33. test('should returns a Promise that resolves when cancellation is requested', done => {
  34. let cancel: Canceler
  35. const token = new CancelToken(c => {
  36. cancel = c
  37. })
  38. token.promise.then(value => {
  39. expect(value).toEqual(expect.any(Cancel))
  40. expect(value.message).toBe('Operation has been canceled.')
  41. done()
  42. })
  43. cancel!('Operation has been canceled.')
  44. })
  45. })
  46. describe('throwIfRequested', () => {
  47. test('should throws if cancellation has been requested', () => {
  48. let cancel: Canceler
  49. const token = new CancelToken(c => {
  50. cancel = c
  51. })
  52. cancel!('Operation has been canceled.')
  53. try {
  54. token.throwIfRequested()
  55. fail('Expected throwIfRequested to throw.')
  56. } catch (thrown) {
  57. if (!(thrown instanceof Cancel)) {
  58. fail('Expected throwIfRequested to throw a Cancel, but test threw ' + thrown + '.')
  59. }
  60. expect(thrown.message).toBe('Operation has been canceled.')
  61. }
  62. })
  63. test('should does not throw if cancellation has not been requested', () => {
  64. const token = new CancelToken(() => {
  65. // do nothing
  66. })
  67. token.throwIfRequested()
  68. })
  69. })
  70. describe('source', () => {
  71. test('should returns an object containing token and cancel function', () => {
  72. const source = CancelToken.source()
  73. expect(source.token).toEqual(expect.any(CancelToken))
  74. expect(source.cancel).toEqual(expect.any(Function))
  75. expect(source.token.reason).toBeUndefined()
  76. source.cancel('Operation has been canceled.')
  77. expect(source.token.reason).toEqual(expect.any(Cancel))
  78. expect(source.token.reason!.message).toBe('Operation has been canceled.')
  79. })
  80. })
  81. })

注意,这里我们使用了 fail 函数表示一个测试的失败,这个并未在 Jest 文档中体现,但它是一个可以用的 API。

Cancel 业务逻辑单元测试

cancel.spec.ts

  1. import axios from '../src/index'
  2. import { getAjaxRequest } from './helper'
  3. describe('cancel', () => {
  4. const CancelToken = axios.CancelToken
  5. const Cancel = axios.Cancel
  6. beforeEach(() => {
  7. jasmine.Ajax.install()
  8. })
  9. afterEach(() => {
  10. jasmine.Ajax.uninstall()
  11. })
  12. describe('when called before sending request', () => {
  13. test('should rejects Promise with a Cancel object', () => {
  14. const source = CancelToken.source()
  15. source.cancel('Operation has been canceled.')
  16. return axios
  17. .get('/foo', {
  18. cancelToken: source.token
  19. })
  20. .catch(reason => {
  21. expect(reason).toEqual(expect.any(Cancel))
  22. expect(reason.message).toBe('Operation has been canceled.')
  23. })
  24. })
  25. })
  26. describe('when called after request has been sent', () => {
  27. test('should rejects Promise with a Cancel object', done => {
  28. const source = CancelToken.source()
  29. axios
  30. .get('/foo/bar', {
  31. cancelToken: source.token
  32. })
  33. .catch(reason => {
  34. expect(reason).toEqual(expect.any(Cancel))
  35. expect(reason.message).toBe('Operation has been canceled.')
  36. done()
  37. })
  38. getAjaxRequest().then(request => {
  39. source.cancel('Operation has been canceled.')
  40. setTimeout(() => {
  41. request.respondWith({
  42. status: 200,
  43. responseText: 'OK'
  44. })
  45. }, 100)
  46. })
  47. })
  48. test('calls abort on request object', done => {
  49. const source = CancelToken.source()
  50. let request: any
  51. axios
  52. .get('/foo/bar', {
  53. cancelToken: source.token
  54. })
  55. .catch(() => {
  56. expect(request.statusText).toBe('abort')
  57. done()
  58. })
  59. getAjaxRequest().then(req => {
  60. source.cancel()
  61. request = req
  62. })
  63. })
  64. })
  65. describe('when called after response has been received', () => {
  66. test('should not cause unhandled rejection', done => {
  67. const source = CancelToken.source()
  68. axios
  69. .get('/foo', {
  70. cancelToken: source.token
  71. })
  72. .then(() => {
  73. window.addEventListener('unhandledrejection', () => {
  74. done.fail('Unhandled rejection.')
  75. })
  76. source.cancel()
  77. setTimeout(done, 100)
  78. })
  79. getAjaxRequest().then(request => {
  80. request.respondWith({
  81. status: 200,
  82. responseText: 'OK'
  83. })
  84. })
  85. })
  86. })
  87. })

注意这里我们使用了 done.fail 表示了一个异常的结束,这个并未在 Jest 文档中体现,但它是一个可以用的 API。

至此,我们完成了取消模块相关业务逻辑的单元测试,我们测试覆盖率达到了阈值,测试已经通过了。但是扔未达到我们的目标,还有很多 feature 是没有覆盖到的。接下来我们就完成剩余 feature 的编写单元测试。