Home > Software engineering >  How to spy a class instantiated within beforeEach in Jest?
How to spy a class instantiated within beforeEach in Jest?

Time:04-28

I have a class instance that should be cleared between each test, so I'm doing this:

class Foo {
  constructor() {}
  public bar() {
    return console.log("bar")
  }
}

describe('tests on class Foo', () => {
  let foo: Foo
  beforeEach(() => {
    foo = new Foo()
    jest.clearAllMocks()
  })

  const spyOnBar = jest.spyOn(foo, "bar")

  test("should call bar", () => {
    foo.bar()
    expect(spyOnBar).toHaveBeenCalled()      
  })
})

but spyOnBar keeps giving me the error Cannot spyOn on a primitive value; undefined given.

there's a way to spy on method bar without declaring the spy inside each test?

CodePudding user response:

See Order of execution of describe and test blocks.

Let's see the order of the execution.

describe('tests on class Foo', () => {
  beforeEach(() => {
    console.log('beforeEach');
  });

  console.log('describe');

  test('should call bar', () => {
    console.log('test');
  });
});

The logs:

 PASS  stackoverflow/72035574/foo.test.ts (10.376 s)
  tests on class Foo
    ✓ should call bar (2 ms)

  console.log
    describe

      at Suite.<anonymous> (stackoverflow/72035574/foo.test.ts:16:11)

  console.log
    beforeEach

      at Object.<anonymous> (stackoverflow/72035574/foo.test.ts:13:13)

  console.log
    test

      at Object.<anonymous> (stackoverflow/72035574/foo.test.ts:20:13)

When you call jest.spyOn(foo, 'bar') method in describe block, the foo instance is not created yet. That's why you got the error.

Option 1. Create the foo instance in the describe block and add spy on its method. Since all test cases share one foo instance and spy, we need to call jest.clearAllMocks() in beforeEach hook to clear the mock.calls and mock.instances properties so that each test case will use a clear spy object. The toBeCalledTimes(1) will work.

class Foo {
  constructor() {}
  public bar() {
    return console.log('bar');
  }
}

describe('tests on class Foo', () => {
  const foo = new Foo();
  const spyOnBar = jest.spyOn(foo, 'bar');
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('should call bar', () => {
    foo.bar();
    expect(spyOnBar).toBeCalledTimes(1);
  });

  test('should call bar - 2', () => {
    foo.bar();
    expect(spyOnBar).toBeCalledTimes(1);
  });
});

Option 2. Create foo and spy in beforeEach hook so that each test case will use a new one. There is no need to use jest.clearAllMocks().

describe('tests on class Foo', () => {
  let spyOnBar: jest.SpyInstance;
  let foo: InstanceType<typeof Foo>;
  beforeEach(() => {
    foo = new Foo();
    spyOnBar = jest.spyOn(foo, 'bar');
  });

  test('should call bar', () => {
    foo.bar();
    expect(spyOnBar).toBeCalledTimes(1);
  });

  test('should call bar - 2', () => {
    foo.bar();
    expect(spyOnBar).toBeCalledTimes(1);
  });
});
  • Related