1

I am using Jasmine to write some test cases. It looks like this:

Class Definition:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return this.firstName + this.lastName;
  }

  getName () {
    setTimeout(() => {
      return this.getFullName();
    }, 100)
  }
}

module.exports = Person;

Test code:

 const Person = require('./index');

 describe('Test Person methods', () => {
  beforeEach(() => {
    programmer = new Person('John', 'Gray');
    spyOn(programmer, 'getFullName');
    programmer.getName();
  });

  it('getName should call getFullName', () => {
    expect(programmer.getFullName).toHaveBeenCalled();
  })
});

getName is an async function which calls getFullName after some time. Because of this async nature, the it spec executes immediately after getName is called, and so the test fails.

I know that I can use done function of Jasmine to carry out async testing, but I am not sure how to implement it here? In the docs, the done method is called from within the async code only, but that code is not accessible to the test as its handled internally by getName method.

Or is there any other approach which does not use done method?

Here is a working sample of this code.

darKnight
  • 5,651
  • 13
  • 47
  • 87
  • You need to call `done` _after_ the async code was executed, so you need to change a bit class `Person` in order, for instance, to provide a callback to `getName` and call this callback in the async code. – Joel Apr 05 '18 at 20:25
  • @Joel: can you show this in code? You may also try out your approach in the link I have given. – darKnight Apr 05 '18 at 20:26
  • https://repl.it/repls/BronzeGleamingFlashmemory – Joel Apr 05 '18 at 20:27
  • Note it would look better with promise... maybe a matter of taste – Joel Apr 05 '18 at 20:29
  • @Joel You have modified the 'getName' method. I cannot do that as I cannot alter the functionality of the code being tested. – darKnight Apr 05 '18 at 20:29
  • sure I did, because it's necessary. No magic. – Joel Apr 05 '18 at 20:31
  • Possible duplicate of [How to test a function which has a setTimeout with jasmine?](https://stackoverflow.com/questions/10955201/how-to-test-a-function-which-has-a-settimeout-with-jasmine) – Heretic Monkey Apr 05 '18 at 20:44

2 Answers2

1

I think you need to pass the done parameter to the function and call it like so:

beforeEach((done) => {
    someObj = new SomeClass();
    spyOn(someObj, 'onSuccess');
    someObj.start();
    done();
});

it('start should call onSuccess', (done) => {
    expect(someObj.onSuccess).toHaveBeenCalled();
    done();
});

Source: Jasmine Documentation

Niels de Bruin
  • 705
  • 5
  • 15
  • Hmm, what version of jasmine are you using? Since the docs say the API has changed in version 2.0. Also maybe you could create a Codepen/JSFiddle/Codesandbox so I can try the code myself to find the solution. – Niels de Bruin Apr 05 '18 at 19:47
0

Your implementation of getName is wrong. In javascript, to handle results from asynchronous functions you either need to pass a callback to your async function or return a Promise. Once you fix your implementation, then testing it should be straightforward:

Using a callback approach:

getName (callback) {
    setTimeout(() => {
        callback(this.getFullName());
    }, 100)
}

beforeEach(done => {
    ...
    programmer.getName(name => { 
      done(); // at this point the async func. has completed
    });
});

Using a Promise:

getName () {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(this.getFullName());
        }, 100)
    }) 
}

beforeEach(done => {
    ...
    programmer
        .getName()
        .then(name => {
            done(); // at this point the async func. has completed
        });
});

EDIT:

If you're using a Promise you can use async / await as well (Jasmine >= 2.7):

beforeEach(async () => {
    ...
    const name = await programmer.getName();
    // async func has completed here. 
    // await acts like .then() and "waits" for your promise to resolve
    // done wouldn't be needed because there aren't any callbacks
});
Daniel Conde Marin
  • 7,588
  • 4
  • 35
  • 44
  • I am using async await in my actual code, and have added `index-async-test.js` file in the sample code (the link to online complier) to reflect this. The test is passing without using `done`!. Any explanation for how this is working? – darKnight Apr 05 '18 at 21:02
  • @maverick See my edit, but basically `await` is waiting for the promise to resolve, sort of "blocking" before beforeEach exits, which is why you wouldn't need `done` in this case – Daniel Conde Marin Apr 05 '18 at 21:08
  • I have the same `async await` implementation in my actual code, and it's failing there. Even though `await` pauses the `async` function, it does not actually stop JavaScript execution, and so the parser will move on to process the next code. When the promise resolves, the functions execution will resume. So in the test case, the `it` block should execute immediately after `programmer.getName()`, and the test should actually fail. Maybe its passing in the online compiler because of incorrect simulation. – darKnight Apr 06 '18 at 06:10
  • @maverick What version of jasmine are you using? `async / await` is only supported since 2.7. Docs https://jasmine.github.io/tutorials/async – Daniel Conde Marin Apr 06 '18 at 08:04
  • I am using Jasmine 3.10.6 and karma-jasmine 3.10.6 – darKnight Apr 06 '18 at 09:51