spyOn question regarding function that return promise - unit-testing

This is a rather general question about spyOn when writing unit tests with functions that return promises in a Vue component.
The way I write test is the following:
// Basically this function gets data from a service and sets some data in the component.
function getSomething() {
ServiceX.getSomething().then(response =>
this.x = response.x
)
}
the test:
describe('test', () => {
beforeEach() => {
vm = shallowMount(VueComponent)
spyOn(serviceX, 'getSomething).and.returnValue(promsie.resolve(data));
}
it('should set X', () =>{
vm.getSomething()
expect(vm.X).toBe(X);
})
}
The issue is, when I do the test this way, the variable X is not set yet, but if I do the "it" statement async and await for the getSomething() method it works.
I wonder if there is another way to do this.

Because your original method returns a promise, and your spy is also returning a promise (even an already resolved) you should use the then or the async await as you commented in the question.
So, the other way of doing is:
it('should set X', (done) => {
vm.getSomething().then(() => {
expect(vm.X).toBe(X);
done();
});
})
Using the done parameter from jasmine to notify this unit test is asynchronous and will complete when this callback is called.

i know it's a little bit too late and you probably already found a solution, but in case you didn't i think that the solution i will propose will work for you. You can use the combination of fakeAsync and tick to test your asynchronous function, bellow is my solution.
describe('test', () => {
...
it('should set X', fakeAsync(() =>{
vm.getSomething();
tick();
expect(vm.X).toBe(X);
}));
...
}

Related

React Native: Jest mocking Platform

When writing unit tests for a React Native project I want to be able to test different snapshots based on different platforms.
I first tried jest.mock to mock Platform but seems to be async. This approach does work when I have two separate files, but I'd prefer to keep everything in one file if possible.
I tried jest.doMock because of this snippet from the documentation:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://facebook.github.io/jest/docs/en/jest-object.html#jestdomockmodulename-factory-options
However I'm still seeing undesirable results. When I console.log in the android test I see that Platform.OS is whatever I set the first doMock to be.
I also tried wrapping the mock in a beforeEach in a describe becasue I thought that might help with scoping
http://facebook.github.io/jest/docs/en/setup-teardown.html#scoping
describe('ios test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'ios';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('android test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'android';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
Any ideas on how I can change the mock Platform for tests in the same file?
There are a lot of suggestions on how to solve this problem in another question, but none of them worked for me either, given the same requirements you have (tests for different OSs in the same suite file and in one test run).
I eventually worked around it with a somewhat clunky trivial helper function that can be mocked as expected in tests – something like:
export function getOS() {
return Platform.OS;
}
Use it instead of Platform.OS in your code, and then simply mock it in your tests, e.g.
it('does something on Android', () => {
helpers.getOS = jest.fn().mockImplementationOnce(() => 'android');
// ...
}
That did the trick; credit for the idea is due to this guy.

Angular2 async unit testing with jasmine

I'm writing an angular2 app and having trouble understanding how to write tests for async code using jasmine. For whatever reason, I'm not seeing a lot of examples that seem terribly applicable to my situation.
I'm currently trying to test a service (not a component) that has a async dependency on another service. I'm not 100% sure at what point in the test it's valid to check the results of the async call. Can you just call expect() inside the async handler for your service?
service.foo()
.then((data) => {
//do I check the results in here?
expect(data).toEqual({ a: 1, b: 2 });
expect(mockDep.get).toHaveBeenCalled();
});
Here is the full test.
import { TestBed, inject } from '#angular/core/testing';
import { MyService } from './my.service.ts';
import { MyDependency } from './dependency.service.ts';
class MockDependency {
doSomething(): Promise<any> {
throw Error('not implemented');
};
}
describe('some tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MyService,
{
provide: MyDependency, useClass: MockDependency
}
]
});
});
});
it('should do something', inject([MyService, MyDependency], (service: MyService, mockDep: MyDependency) => {
spyOn(mockDep, 'doSomething').and.callFake(function () {
return Promise.resolve({ a: 1, b: 2 });
});
service.foo()
.then((data) => {
//do I check the results in here?
expect(data).toEqual({ a: 1, b: 2 });
expect(mockDep.get).toHaveBeenCalled();
});
}));
There's two aspects of dealing with async tests that you have to concern yourself about if you want to ensure your tests are actually reliable.
First, you have to ensure that if a result is retrieved asynchronously, that you wait until the result is available before you try to test for it.
Hence, if the asynchronous result is a promise, for eg, you can put your expect in your then handler, just as you indicated in your question.
The second issue that you have to concern yourself with is forcing your test itself to wait for your expectations to execute before giving a positive (or negative) result. If you don't deal with this, you can have cases where your expectation fails, but because your test did not wait for your asynchronous action to complete before finishing, the test reports a false positive.
There are several ways of 'making your test wait'.
The pure jasmine way is to pass a done handler into your it function. Then jasmine will wait until that done handler is called before considering the test complete.
eg.
it('tests an async action', (done) => {
asyncAction().then(result => {
expect(result).toEqual(true);
done();
});
});
However, angular's testing framework adds two other options to this. The first is easier to grasp if you are comfortable with async programming.
it('tests an async action', async(() => {
asyncAction().then(result => {
expect(result).toEqual(true);
});
}));
in this case, you basically wrap your test handler in an async function. This function will force the test to wait for any async results (eg promises, observables etc) to return a result, before allowing the test to complete.
The second method is to use fakeAsync, which allows you to hide the async nature of your test entirely.
it('tests an async action', fakeAsync(() => {
let myResult;
asyncAction().then(result => {
myResult = result;
});
tick(); <--- force all async actions to complete
expect(myResult).toEqual(true);
}));
fakeAsync hooks into all async functions and allows you to treat them as synchronous. You can use the tick() function to 'force your test to wait' for async tasks to complete before continuing.
(It's not quite doing that, but conceptually, you can think of it that way).
See the Angular docs to learn more

Chai assertion not working as expected using a promise [duplicate]

I'm using Mocha to test an asynchronous function that returns a promise.
What's the best way to test that the promise resolves to the correct value?
Mocha has built-in Promise support as of version 1.18.0 (March 2014). You can return a promise from a test case, and Mocha will wait for it:
it('does something asynchronous', function() { // note: no `done` argument
return getSomePromise().then(function(value) {
expect(value).to.equal('foo');
});
});
Don't forget the return keyword on the second line. If you accidentally omit it, Mocha will assume your test is synchronous, and it won't wait for the .then function, so your test will always pass even when the assertion fails.
If this gets too repetitive, you may want to use the chai-as-promised library, which gives you an eventually property to test promises more easily:
it('does something asynchronous', function() {
return expect(getSomePromise()).to.eventually.equal('foo');
});
it('fails asynchronously', function() {
return expect(getAnotherPromise()).to.be.rejectedWith(Error, /some message/);
});
Again, don't forget the return keyword!
Then 'returns' a promise which can be used to handle the error. Most libraries support a method called done which will make sure any un-handled errors are thrown.
it('does something asynchronous', function (done) {
getSomePromise()
.then(function (value) {
value.should.equal('foo')
})
.done(() => done(), done);
});
You can also use something like mocha-as-promised (there are similar libraries for other test frameworks). If you're running server side:
npm install mocha-as-promised
Then at the start of your script:
require("mocha-as-promised")();
If you're running client side:
<script src="mocha-as-promised.js"></script>
Then inside your tests you can just return the promise:
it('does something asynchronous', function () {
return getSomePromise()
.then(function (value) {
value.should.equal('foo')
});
});
Or in coffee-script (as per your original example)
it 'does something asynchronous', () ->
getSomePromise().then (value) =>
value.should.equal 'foo'

Testing basic navigation in Angular 2 with fakeAsync() instead of Jasmine's done()

I currently have a working test using Gherkin steps and Jasmine async done() for basic navigation as such:
User_Navigates_To_URL(urlPath: string) {
return this.router.navigateByUrl(urlPath);
}
User_Will_Be_On_URL(expectedPath: string) {
expect(this.location.path()).toBe(expectedPath);
}
it('Scenario: User should be able to navigate', (done) => {
When.User_Navigates_To_URL('/associates/your-team').then(() => {
Then.User_Will_Be_On_URL('/associates/your-team');
});
done();
});
But what I'm trying to accomplish is to write this test using fakeAsync instead of jasmine's done() method. This way all async actions will be resolved in the zone and I wont have to nest the assertion step as a callback of the promise. Therefore, I'm attempting to do something like so:
it('Scenario: User should be able to navigate', <any>fakeAsync(() => {
When.User_Navigates_To_URL('/associates/your-team');
tick();
Then.User_Will_Be_On_URL('/associates/your-team');
}));
After weeks of research, the only thing close I found helpful was this question: Does fakeAsync guarantee promise completion after tick/flushMicroservice. But even when I've tried to implement my navigation promise in his snippet, it never resolves.
User_Navigates_To_URL(urlPath: string) {
let currNav:Promise<Router> = this.router.navigateByUrl(urlPath);
let handleNavigation = function (p:Promise<Router>) {
p.then(() => {
console.log('navigation complete')
});
};
let p = Promise.resolve(currNav);
handleNavigation(p);
tick();
}
This is my first question on here so please let me know if my quesiton is confusing or if I need to provide any more details.

Angular2 testing logic before .subscribe

I am currently writing unit tests for one of my components. In particular, I have login(): void function. Here's the simplified logic:
login(): void {
this.showSpinner = true;
this.userService.login(loginData)
.subscribe(result => {
this.showSpinner = false;
}
)
}
I am struggling to write a test that checks that showSpinner property gets set to true before calling the userService.login.
Here's my test:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
fixture.componentInstance.login();
expect(fixture.componentInstance.showSpinner).toBe(true);
tick();
});
})));
});
And this test fails, because .subscribe gets resolved / run immediately (i tried commenting out this.showSpinner = false in my component, and the test passed).
In my userService mock, I have the following, for the login method mock:
this.loginSpy = this.spy('login').andReturn(Observable.of(this));
Where this is mockUserService.
I am confident that I am mocking userService and specifically the login method on the userService correctly, as I have other tests for this component that behave correctly.
I have also tried returning Observable.of(this).delay(1) from my spy and then calling tick(1) in my test. However that results in inconsistent behaviour in that sometimes my tests pass, but other times i get an error saying:
Error: 1 periodic timer(s) still in the queue.
How can I test the logic that precedes .subscribe()?
After more consideration I have realized that my current code does not abide by the single responsibility principle. This thought came from the fact that everyone is always repeating that you should "Refactor hard to test code".
With that in mind, I have moved all the logic that needed to be done before the call to userService.login is being made - into its own separate function. Which essentially results in:
login():void {
this.userService.login(this.loginData)
.subscribe(result => {
this.showSpinner = false;
});
}
formSubmit(): void {
this.showSpinner = true;
this.login();
}
This logic is now much easier to test.
HOWEVER we need to remember to add a spy on our login() method when we are testing formSubmit(), as if we don't, formSubmit() will simply make a call to login(), which will again complete synchronously and we will have the same problem. So my new and final test for this feature is:
it('should display the spinner when the form is being saved',
inject([TestComponentBuilder], fakeAsync((tcb: any) => {
createComponent(tcb).then((fixture:ComponentFixture<any>) => {
var loginSpy = spyOn(fixture.componentInstance, 'login');
fixture.componentInstance.formSubmit();
expect(fixture.componentInstance.showSpinner).toBe(true);
});
})));
});