sergioviniciuss
sergioviniciuss

Reputation: 4696

How to test a method with setTimeout and jquery using jest

I'm struggling a bit to find a solution on how to test this exported function with jest.

export const scrollToError = () => {
  setTimeout(() => {
    const hasErrorElement = jQuery('.has-error');
    if (!hasErrorElement.length) return;
    jQuery('html,body').animate({
      scrollTop: hasErrorElement.offset().top - 50,
    }, 'slow');
  }, 400);
};

I imported it in my test file and tried to start it:

import { scrollToError } from './utils';

describe('Utils', () => {
  it('should scroll to error', () => {
    const result = scrollToError();
    expect(result).toBe(true); //added this just to force an error and got result as undefined 
  });
});

Could anyone give me any tips on how to test code with these dependencies?

Upvotes: 0

Views: 793

Answers (3)

sergioviniciuss
sergioviniciuss

Reputation: 4696

I finally managed to find a proper solution. I wrote three test cases for it:

jest.useFakeTimers();
describe('utils', () => {
  afterEach(() => {
    document.body.innerHTML = '';
  });
  it('ScrollToError - should run the settimeout for 400 ms', () => {
    scrollToError();
    expect(setTimeout).toHaveBeenCalledTimes(1);
    expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 400);
  });
  it('ScrollToError - should scroll to error', () => {
    document.body.innerHTML = formStep1ErrorMock;
    window.setTimeout = fn => fn();
    const result = scrollToError();
    expect(result).toBe(true);
  });
  it('ScrollToError - should do nothing as has no errors', () => {
    document.body.innerHTML = formStep1Mock;
    window.setTimeout = fn => fn();
    const result = scrollToError();
    expect(result).toBe(true);
  });
});

So basically, first I check if the setTimeout was called with the proper amount of seconds (not that it's important).

Then I mock the setTimeout by doing this window.setTimeout = fn => fn(); so it runs without waiting for the delay. I also mock the html with the proper details I need it to have.

And finally, I just cover another scenario.

PS: I added a return true statement to the scrollToError method to make it simpler to have an expected result.

This way I achieved 100% coverage for this method.

Upvotes: 1

Arthur Almeida
Arthur Almeida

Reputation: 568

How are you using jQuery?

I mean, did you get it using npm or yarn? to mock node_modules you can follow this link: https://jestjs.io/docs/en/manual-mocks#mocking-node-modules

Otherwise, you will have to create a manual mock. You can see how to do it here: https://jestjs.io/docs/en/manual-mocks

Updated:

the simplest way is to override it, is while settting up your test at beforeXXX method.

You can simply put something like window.JQuery = jest.fn();

this is the simplest mock ever but you will have to create the methods like animate and other jquery related methods.

Having second thoughts here and looking to your function, if you mock jQuery what else left to be tested?

If you mock, you will be testing if your fn are doing the steps you defined here. Like check if the jQuery fn was called with .has-error class or if animate received the correct parameters.

This kind of test doesn't help you at all, it's just checking if it's following line by line your algorithm. The problem here, that you could do some refactorings like changing the .has-error class name or the animate method by other improved one.

What you really need to change, if it's doing at the end what should be doing. Displaying the div or whatever that should be displayed. If you test that, regardless the way you refactor your code the test will check if the final solution still works and that what matters.

Was I clear? English is not my first language so, it may be a little bit confusing

Upvotes: 1

jayarjo
jayarjo

Reputation: 16754

scrollToError() is asynchronous function and you can't invoke it and expect the result to be there immediately. You need to wait that amount of ms (400 in your case), before testing for it.

Asynchronous code is tested a bit differently in Jest: Testing Asynchronous Code. You can also take control over the timers or combine it all with the manual mocks and override jQuery itself.

Upvotes: 1

Related Questions