Как сделать тест на javascript
Перейти к содержимому

Как сделать тест на javascript

  • автор:

JavaScript unit testing 101

In this blog post I will explain how to write unit tests for JavaScript. For the examples, we will be using jasmine and ts-mockito, but the theory should be applicable to every testing framework. Why am I using two frameworks here instead of only jasmine? Well, in my opinion, jasmine relies too much on magic strings that might give problems during refactoring and makes autocompletion impossible in a lot of cases (depending on how good your IDE is). Ts-mockito is a mocking framework that allows you to create mocks based on a class, so you don’t have to tell it the method names to mock. There are other good mocking frameworks for typescript as well, but my experience is with ts-mockito, so I will be using it for the examples of this blog post. or the code examples I used the Angular CLI to generate a project for me, but I will not be using any of the Angular testing tools to keep these test cases completely framework independent. The Angular CLI was just an easy way to quickly get everything up and running.

The basics

The beginning of a test is naming it. I write every test in the same structure:

This allows you to quickly see from your testing log, which method from which class has a bug. I personally always try to call the object you are testing something like ‘componentUnderTest’ or ‘serviceUnderTest’. This allows you to very quickly see in a unit test, which object is actually being tested.

When you want to mock certain dependencies of your class, you should always declare all the mocks at the top of the test, before your (first) beforeEach call and you should initialize them within the beforeEach. The reason we want to initialize our mocks in the beforeEach and not at declaration, is because you want fresh mocks for every test. If you initialize your mock only once at declaration, the method call count and mock return values will not be removed. This can cause tests to influence each others output, so they can complete successfully or unsuccessfully, depending on the order in which they are run, this can be a headache to debug if you don’t realize this from the beginning. I will explain here with an example:

As you can guess, this test will fail with:

This happens because the tests don’t start with a fresh mock. So the call to the getCars() from the first test, is still part of the callcount of the mock in the second test. If we reinitialize the mocks every time in the beforeEach, this problem is solved.

Writing a unit test

Every unit test has 3 parts. First part is the setup, where you set certain values and mock the necessary method calls. Second part is the actual call of the method you are testing. The last part is the verification, where you will check if the output is correct or the right methods have been called. For readability, leave a blank line between each part, so you can clearly see from a glance what your setup, call and verification part is.

A good way to write your unit test is the following:

You start with your empty test:

Then you write the method call you want to test:

Then you write your verification part:

After this, you run your test once. It should fail. Then you write the setup you need to get to the expected output:

This is a good way to work when you write unit tests. Writing the verification part before your setup, allows you to see that your test is actually able to fail. This is especially handy when you are testing asynchronous code, where a missing done operator can make your test be successful every time, no matter what your expects check, but more about this in the next part.

Asynchronous testing

Thinking in async code, can be a bit of a challenge, especially when you start out with JavaScript as a new developer or coming from a language that works mostly with synchronous code.

In this part of the post, I will show you how to test asynchronous code. Both with promises and observables.

For both promises and observables, I will test methods that show the most common usage patterns. One scenario is where you simply get something asynchronously and set its value to a field, the other scenario is a little more complicated, where you get something asynchronously, have some side effects before it resolves, and have some more side effects after it resolves. If that seems complicated, don’t worry, it will become clear very soon.

For the promises, I will be using the async / await syntax. If you don’t know what that is yet, I suggest you read up on it, a good article is this one.

Promises

Scenario A

The method we will be testing will be the following:

This function just gets the cars from the carService asynchronously and filters out all the non-red cars before returning the array.

The first test, we will simply test the happy scenario, where the carService returns us a nice list of cars and the non-red cars are filtered out.

So what exactly goes on here? First we mock the carService.getCarsAsPromise() method and tell it that it should always return a promise that resolves with a list of cars. Then we call the method that we are testing, we wait for it to resolve (by using the await keyword). Afterward we expect that the returned array only contains the red Porsche. Now, what is this whole try catch thingy? When the promise is rejected, the execution of the code in the try block will be stopped and the code in the catch block will be executed. In this case, if the getCarsAsPromise() should fail, it will go into the catch block and the test will fail. The message that will be shown by jasmine, is the error message that is given to the error that was thrown. For instance, if the getCarsAsPromise() promise return value were to be rejected with the error message ‘Something went wrong.’, the test will fail with the message Failed: Something went wrong. . In the finally block, we call the done function, to tell jasmine that our test is done, we call it in the finally block, because the code in the finally block will always be executed, regardless of the result of the promise.

Note: the done function should ALWAYS be called, regardless if your test fails or succeeds. The reason for this is that without the done function, the test will only fail after the timeout limit has been reached. When jasmine executes a test, it will wait for 5 seconds before timing out, if the done function has not been called within that time, it will fail.

To explain with an example, if we would remove the finally block and call the done function after the expect in the try block, the test would execute as expected in the scenario where the promise is resolved, but if the promise where to be rejected, it would call the fail(e), but since jasmine does not consider the fail call as the end of a test, it would still wait until the timeout for the done function. The test will eventually fail, but it will take 5 seconds to fail instead of 0.5 seconds.

In the second test, we will test what happens when the promise is rejected:

As you can see here, we call the fail function in the try block, because the method under test should throw an error. So after calling the method under test, it should go directly to the catch block. Since our method under test does not handle any errors, there is nothing to expect there. In the finally block, we tell jasmine the test is done.

Scenario B

The next method we will be testing is the following:

So a few things are happening:

  • First we set loading to true
  • Then we get the red cars asynchronously and assign them to our cars field
  • If getting the red cars throws an error, we set carRetrievalError to true
  • Afterward we always set loading back to false

In this case, we don’t just want to test the end result of our test, but also the side effects in between, in this case, the setting of the loading field to true or false. For observables, these types of scenarios are a little easier to test, but with a little creativity, we can also do this for promises, without the use of special testing tools or libraries.

So what’s happening here? In the promise callback, we assign the resolve function to a variable outside of that function, so we can use it later on. Then we call our method under test, but notice that we don’t use the await keyword here. Why is this? Well, if we were to use the await keyword, it would simply wait for the promise to resolve, but it would never happen because our resolve function is never called. Instead we just want to call it, let it execute and start expecting the side effects. As we saw in the method under test, the loading field is set to true before the red cars are retrieved, since those cars are not retrieved yet, we can check that the loading field is indeed set to true. Ok, now we have checked that, let’s resolve the carService call. We do this by calling our promiseResolve function. Now the cars are resolved, so we can check that the loading has now been set to false and the cars field has been correctly set to the right value. The finally callback of the returned promise, will execute after we call our promiseResolve function, and will check the final result of our method and subsequently call the done function to tell jasmine our test is over.

In the second test we will test the error scenario, where we want to see if the loading value is still set correctly and the carRetrievalError is also set to true.

Here we actually do the same thing, except instead of using the resolve function, we use the reject function.

Observables

For observables, I have created methods that do exactly the same as our previous promises scenarios, except we use observables here.

Scenario A

For scenario A we will test a simple observable that returns one value and then completes. This is a fairly common use case, especially with http calls. Here is our method under test:

We simply get all the cars as an observable and then we call the map operator to filter the list we get. Pretty simple scenario.

For a simple scenario like this, there are two ways to test this. The first one is this:

We simply call our method under test, subscribe to the resulting observable, and in the subscribe callback we do our expects and in the error callback we fail the test. This is the easiest way to test this scenario, although, it is a little verbose. To reduce the lines of code, a simple scenario like this, can also be tested by transforming the observable to a promise and test it like we did in the promise scenario:

I guess in this case it depends on what you find the most readable. The async await syntax makes the second version very readable, but comes down to personal preference.

Scenario B

In the next scenario we will be testing an observable call with side effects.

Same scenario as the promises, we set loading to true and subscribe to an observable, when it resolves, we set the value and when it completes, we set loading to false. If the observable throws an error, we set carRetrievalError to true.

So our first test is the happy path, the observable resolves with an array of cars and everything goes as expected:

This scenario is very easily tested with observables, because of Subjects. What is a subject? A subject can act as both an observable and an observer. This means we can subscribe to it and receive the emitted values, or we can tell it to emit a certain value.

In this case, instead of telling the carService to return an Observable that immediately resolves with a value, we tell it to return a subject.asObservable() . Now we are in control of when and what value is emitted in this observable. This allows us to check our side effects easily at the right time.

Note that I am calling the carsSubject.complete() after I emit the car array value. I do this because I set loading to false in the finalize operator. This finalize operator is only called when the observable is completed. So if I don’t call the carsSubject.complete() , loading will never be set to false and my test will fail. The error scenario is pretty much the same logic, except we call the error method instead of the next on the subject.

Conclusion

I hope these code examples help you to effectively unit test your (asynchronous) code. Readability is everything, so make sure you use blank lines to clearly differentiate between the different parts of a test. And don’t forget that done function, without it, your test might timeout, or even worse, it might succeed without actually having tested anything at all.

All the code examples can be checked on the following repository:

Simply clone the repo, run npm install and run npm test to execute the tests.

Happy testing everyone!

Share on LinkedIn Share on Twitter Share on Facebook

Ivar is a backend Java developer who converted to frontend. When ES6 and TypeScript came along, he really started investing his time in that and now he writes Javascript / TypeScript almost exclusively.

Автоматические тесты при помощи chai и mocha

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/testing-mocha.

В этой главе мы разберём основы автоматического тестирования. Оно будет применяться далее в задачах, и вообще, входит в «образовательный минимум» программиста.

Зачем нужны тесты?

При написании функции мы обычно представляем, что она должна делать, какое значение на каких аргументах выдавать.

В процессе разработки мы время от времени проверяем, правильно ли работает функция. Самый простой способ проверить – это запустить её, например в консоли, и посмотреть результат.

Если что-то не так, поправить, опять запустить – посмотреть результат… И так «до победного конца».

Но такие ручные запуски – очень несовершенное средство проверки.

Когда проверяешь работу кода вручную – легко его «недотестировать».

Например, пишем функцию f . Написали, тестируем с разными аргументами. Вызов функции f(a) работает, а вот f(b) не работает. Поправили код – стало работать f(b) , вроде закончили. Но при этом забыли заново протестировать f(a) – упс, вот и возможная ошибка в коде.

Автоматизированное тестирование – это когда тесты написаны отдельно от кода, и можно в любой момент запустить их и проверить все важные случаи использования.

BDD – поведенческие тесты кода

Мы рассмотрим методику тестирования, которая входит в BDD – Behavior Driven Development. Подход BDD давно и с успехом используется во многих проектах.

BDD – это не просто тесты. Это гораздо больше.

Тесты BDD – это три в одном: И тесты, И документация, И примеры использования.

Впрочем, хватит слов. Рассмотрим примеры.

Разработка pow: спецификация

Допустим, мы хотим разработать функцию pow(x, n) , которая возводит x в целую степень n , для простоты n≥0 .

Ещё до разработки мы можем представить себе, что эта функция будет делать, и описать это по методике BDD.

Это описание называется спецификация (или, как говорят в обиходе, «спека») и выглядит так:

У спецификации есть три основных строительных блока, которые вы видите в примере выше:

Задаёт, что именно мы описываем, используется для группировки «рабочих лошадок» – блоков it . В данном случае мы описываем функцию pow .

В названии блока it человеческим языком описывается, что должна делать функция, далее следует тест, который проверяет это.

Код внутри it , если реализация верна, должен выполняться без ошибок.

Различные функции вида assert.* используются, чтобы проверить, делает ли pow то, что задумано. Пока что нас интересует только одна из них – assert.equal , она сравнивает свой первый аргумент со вторым и выдаёт ошибку в случае, когда они не равны. В данном случае она проверяет, что результат pow(2, 3) равен 8 .

Есть и другие виды сравнений и проверок, которые мы увидим далее.

Поток разработки

Как правило, поток разработки таков:

  1. Пишется спецификация, которая описывает самую базовую функциональность.
  2. Делается начальная реализация.
  3. Для проверки соответствия спецификации мы задействуем фреймворк (в нашем случае Mocha). Фреймворк запускает все тесты it и выводит ошибки, если они возникнут. При ошибках вносятся исправления.
  4. Спецификация расширяется, в неё добавляются возможности, которые пока, возможно, не поддерживаются реализацией.
  5. Идём на пункт 2, делаем реализацию. И так «до победного конца».

Разработка ведётся итеративно: один проход за другим, пока спецификация и реализация не будут завершены.

В нашем случае первый шаг уже завершён, начальная спецификация готова, хорошо бы приступить к реализации. Но перед этим проведём «нулевой» запуск спецификации, просто чтобы увидеть, что уже в таком виде, даже без реализации – тесты работают.

Пример в действии

Для запуска тестов нужны соответствующие JavaScript-библиотеки.

Мы будем использовать:

    – эта библиотека содержит общие функции для тестирования, включая describe и it . – библиотека поддерживает разнообразные функции для проверок. Есть разные «стили» проверки результатов, с которыми мы познакомимся позже, на текущий момент мы будем использовать лишь assert.equal . – для эмуляции и хитрой подмены функций «заглушками», понадобится позднее.

Эти библиотеки позволяют тестировать JS не только в браузере, но и на сервере Node.JS. Здесь мы рассмотрим браузерный вариант, серверный использует те же функции.

Пример HTML-страницы для тестов:

Эту страницу можно условно разделить на четыре части:

  1. Блок <head> – в нём мы подключаем библиотеки и стили для тестирования, нашего кода там нет.
  2. Блок <script> с реализацией спецификации, в нашем случае – с кодом для pow .
  3. Далее подключаются тесты, файл test.js содержит describe("pow", . ) , который был описан выше. Методы describe и it принадлежат библиотеке Mocha, так что важно, что она была подключена выше.
  4. Элемент <div > будет использоваться библиотекой Mocha для вывода результатов. Запуск тестов инициируется командой mocha.run() .

Пока что тесты не проходят, но это логично – вместо функции стоит «заглушка», пустой код.

Пока что у нас одна функция и одна спецификация, но на будущее заметим, что если различных функций и тестов много – это не проблема, можно их все подключить на одной странице. Конфликта не будет, так как каждой части кода соответствует свой блок describe("что тестируем". ) . Сами же тесты обычно пишутся так, чтобы не влиять друг на друга, не делать лишних глобальных переменных.

Начальная реализация

Пока что, как видно, тесты не проходят, ошибка сразу же. Давайте сделаем минимальную реализацию pow , которая бы работала нормально:

О, вот теперь работает:

Исправление спецификации

Функция, конечно, ещё не готова, но тесты проходят. Это ненадолго 🙂

Здесь мы видим ситуацию, которая (и не обязательно при ленивом программисте!) бывает на практике – да, есть тесты, они проходят, но функция (увы!) работает неправильно.

С точки зрения BDD, ошибка при проходящих тестах – вина спецификации.

В первую очередь не реализация исправляется, а уточняется спецификация, пишется падающий тест.

Сейчас мы расширим спецификацию, добавив проверку на pow(3, 4) = 81 .

Здесь есть два варианта организации кода:

Первый вариант – добавить assert в тот же it :

Второй вариант – сделать два теста:

Их принципиальное различие в том, что если assert обнаруживает ошибку, то он тут же прекращает выполнение блока it . Поэтому в первом варианте, если вдруг первый assert «провалился», то про результат второго мы никогда не узнаем.

Таким образом, разделить эти тесты может быть полезно, чтобы мы получили больше информации о происходящем.

Кроме того, есть ещё одно правило, которое желательно соблюдать.

Один тест тестирует ровно одну вещь.

Если мы явно видим, что тест включает в себя совершенно независимые проверки – лучше разбить его на два более простых и наглядных.

По этим причинам второй вариант здесь предпочтительнее.

Как и следовало ожидать, второй тест не проходит. Ещё бы, ведь функция всё время возвращает 8 .

Уточнение реализации

Придётся написать нечто более реальное, чтобы тесты проходили:

Чтобы быть уверенными, что функция работает верно, желательно протестировать её на большем количестве значений. Вместо того, чтобы писать блоки it вручную, мы можем сгенерировать тесты в цикле for :

Вложенный describe

Функция makeTest и цикл for , очевидно, нужны друг другу, но не нужны для других тестов, которые мы добавим в дальнейшем. Они образуют единую группу, задача которой – проверить возведение в n -ю степень.

Будет правильно выделить их, при помощи вложенного блока describe :

Вложенный describe объявит новую «подгруппу» тестов, блоки it которой запускаются так же, как и обычно, но выводятся с подзаголовком, вот так:

В будущем мы сможем добавить другие тесты it и блоки describe со своими вспомогательными функциями.

В каждом блоке describe можно также задать функции before/after , которые будут выполнены до/после запуска тестов, а также beforeEach/afterEach , которые выполняются до/после каждого it .

Последовательность будет такой:

Как правило, beforeEach/afterEach ( before/after ) используют, если необходимо произвести инициализацию, обнулить счётчики или сделать что-то ещё в таком духе между тестами (или их группами).

Расширение спецификации

Базовая функциональность pow описана и реализована, первая итерация разработки завершена. Теперь расширим и уточним её.

Как говорилось ранее, функция pow(x, n) предназначена для работы с целыми неотрицательными n .

В JavaScript для ошибки вычислений служит специальное значение NaN , которое функция будет возвращать при некорректных n .

Добавим это поведение в спецификацию:

Результат с новыми тестами:

Конечно, новые тесты не проходят, так как наша реализация ещё не поддерживает их. Так и задумано: сначала написали заведомо не работающие тесты, а затем пишем реализацию под них.

Другие assert

Обратим внимание, в спецификации выше использована проверка не assert.equal , как раньше, а assert(выражение) . Такая проверка выдаёт ошибку, если значение выражения при приведении к логическому типу не true .

Она потребовалась, потому что сравнивать с NaN обычным способом нельзя: NaN не равно никакому значению, даже самому себе, поэтому assert.equal(NaN, x) не подойдёт.

Кстати, мы и ранее могли бы использовать assert(value1 == value2) вместо assert.equal(value1, value2) . Оба этих assert проверяют одно и тоже.

Однако, между этими вызовами есть отличие в деталях сообщения об ошибке.

При «упавшем» assert в примере выше мы видим "Unspecified AssertionError" , то есть просто «что-то пошло не так», а при assert.equal(value1, value2) будут дополнительные подробности: expected 8 to equal 81 .

Поэтому рекомендуется использовать именно ту проверку, которая максимально соответствует задаче.

Вот самые востребованные assert -проверки, встроенные в Chai:

  • assert(value) – проверяет что value является true в логическом контексте.
  • assert.equal(value1, value2) – проверяет равенство value1 == value2 .
  • assert.strictEqual(value1, value2) – проверяет строгое равенство value1 === value2 .
  • assert.notEqual , assert.notStrictEqual – проверки, обратные двум предыдущим.
  • assert.isTrue(value) – проверяет, что value === true
  • assert.isFalse(value) – проверяет, что value === false
  • …более полный список – в документации

В нашем случае хорошо бы использовать проверку assert.isNaN , и такой метод существует, но сейчас мы рассматриваем самый общий метод assert(. ) . В этом случае для того, чтобы сделать сообщение об ошибке понятнее, желательно добавить к assert описание.

Все вызовы assert позволяют дополнительным последним аргументом указать строку с описанием ошибки, которое выводится, если assert не проходит.

Добавим описание ошибки в конец наших assert’ов :

Теперь результат теста гораздо яснее говорит о том, что не так:

В коде тестов выше можно было бы добавить описание и к assert.equal , указав в конце: assert.equal(value1, value2, "описание") , но с равенством обычно и так всё понятно, поэтому мы так делать не будем.

Итого

Итак, разработка завершена, мы получили полноценную спецификацию и код, который её реализует.

Задачи выше позволяют дополнить её, и в результате может получиться что-то в таком духе:

Эту спецификацию можно использовать как:

  1. Тесты, которые гарантируют правильность работы кода.
  2. Документацию по функции, что она конкретно делает.
  3. Примеры использования функции, которые демонстрируют её работу внутри it .

Имея спецификацию, мы можем улучшать, менять, переписывать функцию и легко контролировать её работу, просматривая тесты.

Особенно важно это в больших проектах.

Бывает так, что изменение в одной части кода может повлечь за собой «падение» другой части, которая её использует. Так как всё-всё в большом проекте руками не перепроверишь, то такие ошибки имеют большой шанс остаться в продукте и вылезти позже, когда проект увидит посетитель или заказчик.

Чтобы избежать таких проблем, бывает, что вообще стараются не трогать код, от которого много что зависит, даже если его ну очень нужно переписать. Жизнь пробивается тонкими росточками там, где должна цвести и пахнуть новая функциональность.

Код, покрытый автотестами, являет собой полную противоположность этому!

Даже если какое-то изменение потенциально может порушить всё – его совершенно не страшно сделать. Ведь есть масса тестов, которые быстро и в автоматическом режиме проверят работу кода. И если что-то падает, то это можно будет легко локализовать и поправить.

Кроме того, код, покрытый тестами, имеет лучшую архитектуру.

Конечно, это естественное следствие того, что его легче менять и улучшать. Но не только.

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

Конечно, в реальной жизни всё не так просто. Зачастую написать тест сложно. Или сложно поддерживать тесты, поскольку код активно меняется. Сами тесты тоже пишутся по-разному, при помощи разных инструментов.

Что дальше?

В дальнейшем условия ряда задач будут уже содержать в себе тесты. На них вы познакомитесь с дополнительными примерами.

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

How to Build an Online Quiz Application Using JavaScript with Code Examples

Frontend Weekly

In this tutorial, we will be building an online quiz application using JavaScript. The application will allow users to test their knowledge on various topics and keep track of their scores. We will be using HTML, CSS, and JavaScript to build the application. Let’s get started!

Step 1: Set Up Your HTML

First, we need to set up the HTML structure for our quiz application. We will be using a form element to contain our quiz questions and answer choices. Here’s an example of what the HTML structure might look like:

Note that we have created three questions, each with three answer choices. We have also added a submit button at the end of the form.

Step 2: Create a JavaScript Function to Score the Quiz

Next, we need to create a JavaScript function that will score the quiz. This function will be called when the user submits their answers. Here’s an example of what the function might look like:

In this function, we first initialize a score variable to 0. We then use the querySelector method to get the value of each checked radio button. We check each answer and increment the score if the answer is correct. Finally, we display the score in an alert box.

Step 3: Add Event Listener to the Submit Button

We need to add an event listener to the submit button so that the scoreQuiz function is called when the user submits their answers. Here’s an example of how we can add an event listener to the submit button:

In this code, we first get the quiz form element by its ID. We then add an event listener to the form using the submit event. We use the preventDefault method to prevent the form from submitting and reloading the page. Finally, we call the scoreQuiz function to calculate the user’s score.

Step 4: Style Your Quiz Application

Lastly, we can add some CSS styling to our quiz application to make it look more visually appealing. Here’s an example of what the CSS might look like:

This CSS code will add some basic styling to the quiz application, including centering the form, adjusting the font family and sizes, and styling the submit button.

Here is the Full code

Final Thoughts

Congratulations! You have now built an online quiz application using JavaScript. This project can be expanded by adding more questions, including different question types (e.g. multiple-choice, true/false, fill-in-the-blank), and adding more advanced scoring features.

If you want to see the full code for this project, you can find it on Github Gist and Codepen. If you want to stay updated on more tutorials and projects, be sure to follow me on GitHub and Medium.

Тестирование JavaScript кода с Jest для чайников. Часть 1

И действительно, Jest очень простой. Он не требует дополнительных настроек, легкий в понимании и применении, а так же имеет довольно хорошую документацию. Отлично подходит для проектов использующих Node, React, Angular, Vue, Babel, TypeScript и не только.
Также он имеет открытый исходный код и поддерживается компанией Facebook.

Установка

Для установки Jest в ваш проект выполните:

Если вы используете yarn:

После установки можете обновить секцию scripts вашего package.json:

С помощью такого простого вызова мы уже можем запустить наши тесты (на самом деле jest потребует существование хотя бы одного теста).

Также можно установить глобально (но так делать я бы не рекомендовал, так как по мне глобальная установка модулей является плохой практикой):

И соответственно для yarn:

После этого вы можете использовать jest непосредственно из командной строки.

При помощи вызова команды jest —init в корне проекта, ответив на несколько вопросов, вы получите файл с настройками jest.config.js. Или можно добавить конфигурацию прямиком в ваш package.json. Для этого добавьте в корень json ключ «jest» и в соответствующем ему объекте можете добавлять необходимые вам настройки. Сами опции мы разберем позже. На данном этапе в этом нет необходимости, поскольку jest можно использовать «сходу», без дополнительных конфигураций.

Первый тест

Давайте создадим файл first.test.js и напишем наш первый тест:

И запустим наши тесты с помощью npm run test или непосредственно командой jest (если он установлен глобально). После запуска мы увидим отчет о прохождении тестов.

Давайте «сломаем» наш тест и запустим jest повторно:

Как мы видим, теперь наш тест не проходит проверки. Jest отображает подробную информацию о том, где возникла проблема, какой был ожидаемый результат, и что мы получили вместо него.

Теперь давайте разберём код самого теста. Функция test используется для создания нового теста. Она принимает три аргумента (в примере мы использовали вызов с двумя аргументами). Первый — строка с названием теста, его jest отобразит в отчете. Второй — функция, которая содержит логику нашего теста. Также можно использовать 3-й аргумент — таймаут. Он является не обязательным, а его значение по умолчанию составляет 5 секунд. Задаётся в миллисекундах. Этот параметр необходим когда мы работаем с асинхронным кодом и возвращаем из функции теста промис. Он указывает как долго jest должен ждать разрешения промиса. По истечению этого времени, если промис не был разрешен — jest будет считать тест не пройденным. Подробнее про работу с асинхронными вызовами будет в следующих частях. Также вместо test() можно использовать it(). Разницы между такими вызовами нету. it() это просто алиас на функцию test().

Внутри функции теста мы сначала вызываем expect(). Ему мы передаем значение, которое хотим проверить. В нашем случае, это результат вызова Math.max(1, 5, 10). expect() возвращает объект «обертку», у которой есть ряд методов для сопоставления полученного значения с ожидаемым. Один из таких методов мы и использовали — toBe.

Давайте разберем основные из этих методов:

  • toBe() — подходит, если нам надо сравнивать примитивные значения или является ли переданное значение ссылкой на тот же объект, что указан как ожидаемое значение. Сравниваются значения при помощи Object.is(). В отличие от === это дает возможность отличать 0 от -0, проверить равенство NaN c NaN.
  • toEqual() — подойдёт, если нам необходимо сравнить структуру более сложных типов. Он сравнит все поля переданного объекта с ожидаемым. Проверит каждый элемент массива. И сделает это рекурсивно по всей вложенности.

Далее добавим тесты:

В этих тестах мы проверили результат работы 2-х методов — area и circumference. При помощи метода toBeCloseTo мы сверились с ожидаемым результатом. В первом случае мы проверили или вычисляемая площадь круга с радиусом 5 приблизительно равна 78.54, при этом разница с полученым значением (оно составит 78.53981633974483) не большая и тест будет засчитан. Во втором мы указали, что нас интересует проверка с точностью до 1 знака после запятой. Также мы вызвали наши методы без аргументов и проверили результат с помощью toBeNaN. Поскольку результат их выполнения будет NaN, то и тесты будут пройдены успешно.

Разберём ещё один пример. Создадим функцию, которая будет фильтровать массив продуктов по цене:

В этом тесте мы проверям результат работы функии byRangePrice. Сначала мы проверили соответствие длины полученого массива ожидаемой — 2. Следующая проверка требует, чтобы в массиве находился элемент — < name: 'tomato', price: 26 >. Объект в массиве и объект переданный toContainEqual — это два разных объекта, а не ссылка на один и тот же. Но toContainEqual сверит каждое свойство. Так как оба объекта идентичные — проверка пройдет успешно. Далее мы используем toEqual для провеки структуры всего массива и его элементов. Методы toBeGreaterThanOrEqual и toBeLessThanOrEqual помогут нам проверить price первого и второго элемента массива. И, наконец, вызов not.toContainEqual сделает проверку, не содержится ли в массиве элемент — < name: 'orange', price: 38 >, которого по условию там быть не должно.

В данных примерах мы написали несколько простых тестов используя функции проверки описанные выше. В следующих частях мы разберём работу с асинхронным кодом, функции jest которые не затрагивались в этой части туториала, поговорим о его настройке и многое другое.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *