Jest - Понимание порядка выполнения description() и it()

Я хотел бы понять, почему все блоки describe() работают перед it() внутри каждого describe().

Вот Пример CodeSandbox с журналом для каждого блока describe и it.

describe("sum A", () => {
  window.innerWidth = 100;
  console.info("describe A", window.innerWidth);

  beforeAll(() => {
    window.innerWidth = 105;
    console.info("describe A before", window.innerWidth);
  });

  it("1. should add 1 and 2", () => {
    console.info("it A1", window.innerWidth);
    expect(1 + 2).toBe(3);
  });
});

describe("sum B", () => {
  console.info("describe B", window.innerWidth, "why does it run before it A1?");

  beforeAll(() => {
    window.innerWidth = 205;
    console.info("describe B before", window.innerWidth);
  });

  it("1. should add 1 and 2", () => {
    console.info("it B1", window.innerWidth);
    expect(1 + 2).toBe(3);
  });
});

Вы заметите, что журнал из второго блока описания запускается перед it() внутри первого блока описания.

Почему это происходит? Должны ли мы избегать каких-либо действий в этой части кода и предпочесть использовать beforeAll(), когда нам нужно разделить и ограничить данные в этом блоке описания?

🤔 А знаете ли вы, что...
JavaScript поддерживает асинхронное программирование с использованием промисов и асинхронных функций.


5
2 559
1

Ответ:

Решено

Когда Jest запускается, он ищет все тестовые файлы и запускает каждый из них.

Каждый тестовый файл запускается в среде, предоставляемой Jest, которая включает такие глобальные переменные, как describe, it, beforeAll и т. д. Все эти глобальные переменные имеют параметр обратного вызова, который определяет их поведение.

Когда запускается тестовый файл, запускается код верхнего уровня... включая любые вызовы describe верхнего уровня.

Когда describe запускает регистрирует набор тестов, а затем его обратный вызов вызывается немедленно.

Это отличается от it, beforeAll, beforeEach и т. д., где обратный вызов записывается, но не вызывается сразу.

Это означает, что все функции обратного вызова describe вызываются в глубину в том порядке, в котором они появляются в тестовом файле, как можно увидеть в этом простом примере:

describe('1', () => {
  console.info('1');
  describe('2', () => { console.info('2'); });
  describe('3', () => {
    console.info('3');
    describe('4', () => { console.info('4'); })
    describe('5', () => { console.info('5'); })
  })
  describe('6', () => { console.info('6'); })
})
describe('7', () => {
  console.info('7');
  it('(since there has to be at least one test)', () => { });
})

...который регистрирует 1 - 7 по порядку.

Этот начальный запуск всех обратных вызовов describe называется этап сбора, во время которого определяются наборы тестов и собираются все обратные вызовы для любых beforeAll, beforeEach, it, test и т. д.

После завершения этапа сбора Jest...

runs all the tests serially in the order they were encountered in the collection phase, waiting for each to finish and be tidied up before moving on.

Для каждого теста (каждая функция обратного вызова, зарегистрированная с глобальными функциями it или test) Jestсвязывает вместе любые обратные вызовы до, сам тестовый обратный вызов и любые обратные вызовы после и запускает полученные функции по порядку.


Should we avoid doing stuff in that part of the code and prefer using beforeAll() when we need to share and scope data in that describe block?

Для простых вещей, которыми не делятся, хорошо иметь их в describe:

describe('val', () => {
  const val = '1';

  it('should be 1', () => {
    expect(val).toBe('1');  // Success!
  });
});

... но код в describe может вызвать проблемы с общими данными:

describe('val', () => {
  let val;

  describe('1', () => {
    val = '1';
    it('should be 1', () => {
      expect(val).toBe('1');  // FAIL! (val gets set to 2 in the second describe)
    })
  })

  describe('2', () => {
    val = '2';
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

... что можно исправить либо с помощью вызовов before:

describe('val', () => {
  let val;

  describe('1', () => {
    beforeEach(() => {
      val = '1';
    });
    it('should be 1', () => {
      expect(val).toBe('1');  // Success!
    })
  })

  describe('2', () => {
    beforeEach(() => {
      val = '2';
    });
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

...или просто скопируйте данные в describe:

describe('val', () => {

  describe('1', () => {
    const val = '1';
    it('should be 1', () => {
      expect(val).toBe('1');  // Success!
    })
  })

  describe('2', () => {
    const val = '2';
    it('should be 2', () => {
      expect(val).toBe('2');  // Success!
    })
  })

});

В вашем примере вы используете window.innerWidth, который является общим глобальным, поэтому вы захотите использовать функции before, поскольку он не может быть привязан к describe.


Также обратите внимание, что вы ничего не может вернуть из describe, поэтому, если ваши тесты требуют какой-либо асинхронной настройки, вам нужно будет использовать функцию before, где вы можете вернуть Promise для Jest для ожидания:

const somethingAsync = () => Promise.resolve('1');

describe('val', () => {
  let val;

  beforeAll(async () => {
    val = await somethingAsync();
  });

  it('should be 1', () => {
    expect(val).toBe('1');  // Success!
  });
});