前端测试框架Jest——语法篇

使用匹配器

使用不一样匹配器能够测试输入输出的值是否符合预期。下面介绍一些常见的匹配器。android

普通匹配器正则表达式

最简单的测试值的方法就是看是否精确匹配。首先是toBe()数据库

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});
复制代码

toBe用的是JavaScript中的Object.is(),属于ES6中的特性,因此不能检测对象,若是要检测对象的值的话,须要用到toEqual。toEquel递归检查对象或者数组中的每一个字段。数组

test('object assignment', () => {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1, two: 2});
});
复制代码

Truthinessbash

在实际的测试中,咱们有时候须要区分undefined、null和false。如下的一些规则有助于咱们进行。异步

  • toBeNull只匹配null
  • toBeUndefined只匹配undefined
  • toBeDefine与toBeUndefined相反
  • toBeTruthy匹配任何if语句为真
  • toBeFalsy匹配任何if语句为假

数字匹配器async

大多数的比较数字有等价的匹配器。函数

  • 大于。toBeGreaterThan()
  • 大于或者等于。toBeGreaterThanOrEqual()
  • 小于。toBeLessThan()
  • 小于或等于。toBeLessThanOrEqual()
  • toBe和toEqual一样适用于数字 注意:对比两个浮点数是否相等的时候,使用toBeCloseTo而不是toEqual

例子以下:测试

test('two plus two', () => {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});
复制代码
test('两个浮点数字相加', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);           这句会报错,由于浮点数有舍入偏差
  expect(value).toBeCloseTo(0.3); // 这句能够运行
});
复制代码

若是使用toBe就会产生如下结果:fetch

错误

字符串

使用toMatch()测试字符串,传递的参数是正则表达式。

test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});
复制代码

数组

如何检测数组中是否包含特定某一项?可使用toContain()

const shoppingList = [
  'diapers',
  'kleenex',
  'trash bags',
  'paper towels',
  'beer',
];

test('购物清单(shopping list)里面有啤酒(beer)', () => {
  expect(shoppingList).toContain('beer');
});
复制代码

另外

若是你想在测试特定函数的时候抛出一个错误,在它调用的时候可使用toThrow。

function compileAndroidCode() {
  throw new ConfigError('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
  expect(compileAndroidCode).toThrow();
  expect(compileAndroidCode).toThrow(ConfigError);

  // You can also use the exact error message or a regexp
  expect(compileAndroidCode).toThrow('you are using the wrong JDK');
  expect(compileAndroidCode).toThrow(/JDK/);
});
复制代码

测试异步代码

在实际开发过程当中,咱们常常会遇到一些异步的JavaScript代码。当你有以异步方式运行的代码的时候,Jest须要知道当前它测试的代码是否已经完成,而后它能够转移动另外一个测试。也就是说,测试用例必定要在测试对象结束以后才可以结束

为了达到这一目的,Jest有多种方法能够作到。

回调

最多见的异步模式就是回调函数。

注意:回调函数和异步没有必然的联系,回调只是异步的一种调用方式而已,不要将异步和回调两个概念结合起来谈

好比如下代码:

// 这里是同步执行的,彻底没有异步
function fun1(callback) {
  callback();
}
复制代码

如今假设一个fetchData(call)函数,获取一些数据并在完成的时候调用call(data),而我想要测试返回的数据是否是字符串'peanut butter'

默认状况下,一旦到达运行上下文底部,jest测试就会当即结束。这意味着这个测试将不能按照预期的进行。

function fetchData(call) {
  setTimeout(() => {
    call('peanut butter1')
  },1000);
}

test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter'); // 这里没有执行到
    // done()
  }
  fetchData(callback);
});
复制代码

这样作是不会报错的,由于没有执行到咱们想要测试的语句中的时候Jest测试已经结束了。(一旦fetchData执行结束,此测试就在没有调用回调函数前结束,由于使用了setTimeout,产生了异步)

而咱们能够改为如下: 使用单个参数调用done,而不是将测试放在一个空参数的函数中,Jest会等done回调函数执行结束后,结束测试。

function fetchData(call) {
  setTimeout(() => {
    call('peanut butter1')
  },1000);
}

test('the data is peanut butter', (done) => {
  function callback(data) {
    expect(data).toBe('peanut butter');
    done()
  }
  fetchData(callback);
});
复制代码

可行

若是done()永远不会被调用,则说明这个测试将失败,这也正是咱们所但愿看到的。

Promise

若是咱们的代码中使用到了Promises,只须要从你的测试中返回一个Promise,Jest就会等待这个Promise来解决。若是承诺被拒绝,则测试将会自动失败。

举个例子,若是fetchData,使用Promise代替回调的话,返回值是应该解析为一个字符串'peanut butter'的Promise。那么咱们可使用如下方式进行测试代码:

test('the data is peanut butter', () => {
  expect.assertions(1);
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});
复制代码

注意:必定要返回Promise,若是省略了return语句,测试将会在fetchData完成以前完成。

另一种状况,就是想要Promise被拒绝,咱们可使用.catch方法。另外,要确保添加了expect.assertions来验证必定数量的断言被调用。不然一个fulfilled态的Promise不会让测试失败。

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});
复制代码

.resolves/.rejects

可使用./resolves匹配器匹配你的指望的声明(跟Promise相似),若是想要被拒绝,可使用.rejects

test('the data is peanut butter', () => {
  expect.assertions(1);
  return expect(fetchData()).resolves.toBe('peanut butter');
});
复制代码
test('the fetch fails with an error', () => {
  expect.assertions(1);
  return expect(fetchData()).rejects.toMatch('error');
});
复制代码

Async/Await

若要编写async测试,只要在函数前面使用async关键字传递到test。好比,能够用来测试相同的fetchData()方案

test('the data is peanut butter', async () => {
  expect.assertions(1);
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});
复制代码

setup and teardown

写测试的时候,咱们常常须要进行测试以前作一些准备工做,和在进行测试后须要进行一些整理工做。Jest提供辅助函数来处理这个问题。

为屡次测试重复设置

若是你有一些要为屡次测试重复设置的工做,可使用beforeEach和afterEach。

有这样一个需求,须要咱们在每一个测试以前调用方法initializeCityDatabase(),在每一个测试后,调用方法clearCityDatabase()

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});
复制代码

一次性设置

在某些状况下,你只须要在文件的开头作一次设置。这种设置是异步行为的时候,你不太可能一行处理它。Jest提供了beforeAll和afterAll处理这种状况。

beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});
复制代码

做用域

默认状况下,before和after的块能够应用到文件中的每个测试。此外能够经过describe块来将将测试中的某一块进行分组。当before和after的块在describe块内部的时候,则只适用于该describe块内的测试。

好比说,咱们不只有一个城市的数据库,还有一个食品数据库。咱们能够为不一样的测试作不一样的设置︰

// Applies to all tests in this file
beforeEach(() => {
  return initializeCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

describe('matching cities to foods', () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 sausage', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});
复制代码

注意:顶级的beforeEach描述块内的beforeEach以前执行,如下的例子能够方便咱们认识到执行的顺序

beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach  //特别注意
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
复制代码
相关文章
相关标签/搜索