App & Browser Testing Made Easy

Give your users a seamless experience by testing on 3000+ real devices and browsers. Don't compromise with emulators and simulators

Home Guide Javascript Testing Best Practices to Follow

Javascript Testing Best Practices to Follow

By Mohit Joshi, Community Contributor -

Testing is a process of ensuring whether the developed product works as intended. A lot of tests are written in JavaScript. JavaScript provides top-notch features to make your tests more functional and less flaky. Here are some of the best practices to follow while writing tests in JavaScript. 

1. Don’t use “try…catch”

The “try…catch” statement is used to catch errors made by the programmer. However, it is recommended not to use this approach of wrapping elements with the try-catch method. It is only good to catch errors that are predetermined, whereas a lot of cases are unpredictable. This method separates the program core’s logic from error handling logic, which makes it less reliable for testing purposes.

Let’s consider an example where you will create a function that checks if the two passwords are equal and throw an error when one password is left blank using the try-catch method. 

it('shows error when first password is not given’, () => {
    try {
        isPasswordSame(null, "passWd");
    } catch (error) {
        expect(error.message).toBe("password not found");
    }
});

In the above code, still passes our tests when the second password is not entered and left blank. To come up with a better approach, we can use toThrow assertions, which will help us frame a test where you will get the error when you don’t enter the second password. 

it('shows error when first password is not given', () => {
    expect(() => isPasswordSame(null, "passWd")).toThrow("password not found");
});

2. Don’t use mock 

Mocking is the process of introducing external dependencies on the unit that is being tested. It is done to bring focus to the unit that is being tested and not on the behavior of external dependencies. 

Using mocks in your code is good. However, it is often overused. Do not mock everything, as it makes the tests slow. Mocks should only be used when there is less possibility of including dependencies on our tests, such as when testing HTTP requests. 

Let’s understand with an example in which cases mocking is an effective solution.

function getTimeStamp() {
  const now = new Date();
  const hours = now.getHours();
  const minutes = now.getMinutes();

  return ‘${hours}:${minutes}’
}

In the above example, the function requires time, however, it needs the current time, which keeps on changing. While testing, this will throw an error. 

To resolve this you can use mocking and set an instance of time that will be used when you test.

beforeAll(() => {
  jest.useFakeTimers('modern');
  jest.setSystemTime(new Date('31 October 2022 01:00 IST').getTime());
})

afterAll(() => {
  jest.useRealTimers()
})

test('setting the formatted time', () => {
  expect(getTimestamp()).toEqual('14:32:19')
})

For the purpose of the example, Jest is used. The same is the case with with other frameworks like Babel, TypeScript, Node, React, etc. 

3. Write good test descriptions and use more scenarios

Writing proper test descriptions improves the readability of your code. The goal is to provide ample information while organizing them properly so the developers can quickly get to the section they want. 

For instance, if the developer updates a method in the code, your proper structured code will guide them on which section of the test requires change now. Therefore, it is a good practice to always write test descriptions which improves the readability of the code. 

Here are a few steps you can follow to make your code more structured:

  • Use Describe block effectively while nesting
describe('<HomePage/>', () => {
  it('displays the user as a guest when not logged in', () => {});

  it('prompts the user to login if not logged in', () => {});
  
  it('user proceed as a guest when not logged in', () => {});
});

In the above example, the nested code doesn’t look well-structured, thus introducing a ‘describe’ block here will make it more organized. 

describe('<HomePage/>', () => {
  describe('when user is a guest', () => {
    it('displays the user as a guest', () => {});

    it('prompts the user to login/signup', () => {});
  
    it(‘user proceed as a guest', () => {});
  });
});
  • Write detailed descriptions
describe('ShoppingApp', () => {
    describe('Add to cart', () => {
        it('When item is already in cart, expect item count to increase', async () => {
            // ...
        });

        it('When item does not exist in cart, expect item count to equal one', async () => {
            // ...
        });
    });
});
  • Prevent duplication of code

It is obvious if you’re repeating code, it becomes less readable and maintainable. The goal here is to not repeat the implementation logic of your code. 

4. Don’t overuse helping libraries and test preparation hooks

Helper libraries provide the comfort to work with complex setup requirements by installing all the implementation details automatically. However, extensive use of helper libraries creates confusion for developers, especially when they have not worked on your project very much. Also, this hides the underlying processes and configurations of your project. 

The ultimate aim of your test must be to take the developer on a simplistic journey of code throughout the testing. This is easily achievable when you’re not using any helper libraries. It reduces the time for the developer to figure out what’s going on in the code and where to bring changes. 

Apart from helper libraries, extensive use of test preparation hooks (beforeAll, beforeEach, etc.,) also brings complexity to your code. It creates confusion for developers in debugging.

5. Incorporate a suitable naming convention

Having a good structure of your code is one thing, and incorporating a suitable naming convention to them is another. Both go hand in hand and must be practiced mindfully. It is always worth assigning proper names to the scenarios used in your test. 

The idea behind assigning a proper naming is to not leave any gap between you and your team members while collaborating on a project.

6. Keep in mind the cross browsing aspect

Your web application is going to be accessed by a wide range of browsers, therefore it becomes necessary that you adopt code in such a manner that is accessed in all the major browsers and operating systems flawlessly. You can use cross browser testing tools like BrowserStack to test your apps and websites on different desktop and mobile browsers.

Try BrowserStack for free 

7. Always use the BDD approach 

BDD stands for Behaviour Driven Development and is an extension of TDD. The idea behind BDD is to fill whatever there is a gap in communication between all the stakeholders of the project. Following this practice allows more creative ideas to flow in the project which will ultimately improve the tests. BDD is written with the help of Gherkin language, which is simple English, however, each scenario written with Gherkin language following the BDD approach is binded to its necessary source code by a tester. 

Here’s an example of how to follow the BDD approach.

Feature: Signin
   
@smoke
Scenario: Signin to bstackdemo website
   Given I open bstackdemo homepage
    And I click signin link
    And I enter the login details
    And I click login button
    Then Profile Name should appear
 
Tags
Automation Frameworks Automation Testing

Featured Articles

Top JavaScript Testing Frameworks

Common JavaScript Errors every Tester should know

Curated for all your Testing Needs

Actionable Insights, Tips, & Tutorials delivered in your Inbox
By subscribing , you agree to our Privacy Policy.
thank you illustration

Thank you for Subscribing!

Expect a curated list of guides shortly.