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);
});
})));
});
Related
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);
}));
...
}
I have a Vue component/view that performs an API request using Axios and updates the component data with the response. I'm using Moxios to mock the Axios request in unit tests.
I tried using Vue.nextTick to postpone assertion of the updated data, but the component has not updated at that point yet. If I add a delay, the assertion works correctly:
setTimeout(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
}, 500)
However this is bad practice, slows down the tests and is a race condition.
Is there some kind of assertion check that would be called every time a component updates? Essentially, I'm looking for something like:
Vue.eventually(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
})
A generic (non-Vue-specific) workaround:
const test = () => {
try {
expect(wrapper.text()).toMatch('Updated text')
done()
} catch (e) {
setTimeout(test, 1)
}
}
setTimeout(test, 1)
However, any failures from the expect are ignored and the test times out without any message if failing.
I have the next code i made a test but now i have a dilema strictly speaking with tdd for add
"this.loadCounter('anotherReq', 'anotherError', differentCallback);"
i have to reply the tests only for test the behavior but i not sure if this is necessary.
class Statistic extends PureComponent<Props, State> {
state = {
};
componentDidMount() {
this.loadCounter('suggestedReqCount', 'hasSuggestedReqsErrors', getCountSuggestedReqs);
this.loadCounter('anotherReq', 'anotherError', differentCallback);
}
loadCounter = async (
stateCounterKey: string, stateCounterErrorKey: string, loaderFunction: Function) => {
try {
const count = await loaderFunction();
this.setState({
[stateCounterKey]: count,
});
} catch (ex) {
this.setState({
[stateCounterErrorKey]: true,
});
}
}
}
this are the test that i have
test('Should set state with suggested requirement count', async () => {
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().suggestedReqCount).toBe(5);
});
test('Should on fail load suggested reqs update state', async () => {
getCountSuggestedReqs.mockReturnValueOnce(Promise.reject('Error creado'));
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().hasSuggestedReqsErrors).toBe(true);
});
The first rule of TDD is that we cannot write any production code before having a failing test.
This alone implies that if you want to add production code, you have to write a test that will require it.
That said, there are cases where I won't write a unit test and let the larger tests grab that functionality, like in cases of simple delegation - the code isn't doing any logic, just passing the same arguments to another function, so there's nothing really to test, and the wiring will be tested from the outside.
In this case, however, it seems to me that you're invoking that function twice in order to get different information.
You could expand the existing test to include the new data, but I'm not a fan of that option. I would rather leave existing tests and add new tests for new functionality. Notice that "As the tests become more specific, the code becomes more generic", is true here too. Perhaps when the third call to loadCounter comes in, you'll need to refactor the code to be more generic, but at this point I would just add the second unit test.
All that said, you should definitely separate your logic code and your component code. There's no reason this unit test would be an Enzyme test. The code in componentDidMount could be easily refactored to a function that could be unit tested by just a plain ol' test.
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.
Im working on a test suite for an existing Backbone application using Jasmine and Sinon and I am testing that my router performs the correct actions on a certain route. Here's the actual route function:
favourites: function()
{
//Dont re-initialize the favourites view as there is no need.
//Instead, just render the favourite movies
if ( ! this.favMoviesView)
{
this.favMoviesView = new cinephile.Views.FavouriteMoviesView({
collection: cinephile.favouriteMovies
});
}
else
{
this.favMoviesView.renderFavourites();
}
$('#content').html(this.favMoviesView.el);
},
In my test suite I want to assert that when navigating to to the favourites route this.favMoviesView will be created once and then, if it exists will not re-initialize but instead just call this.favMoviesView.renderFavourites() which is a method that iterates over the view's collection.
Here's my test spec:
describe('cinephile.Routers.CinephileRouter', function () {
beforeEach(function () {
this.router = new cinephile.Routers.CinephileRouter();
this.routeSpy = sinon.spy();
try
{
Backbone.history.start({ silent : true });
}
catch(e) {}
this.router.navigate('elsewhere');
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView')
.returns(new Backbone.View());
});
afterEach(function () {
this.favouritesViewStub.restore();
});
describe('Favourites Route', function() {
it('should load the favourites on /favourites', function () {
this.router.bind('route:favourites', this.routeSpy);
this.router.navigate('favourites', true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith()).toBeTruthy();
});
it('creates a favourites view if one doesn\'t exist', function () {
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
});
it('Reuses the favourites view if one does exist and reset it\'s collection', function () {
this.router.favourites();
this.router.favourites();
expect(this.favouritesViewStub.calledOnce).toBeTruthy();
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
});
});
});
My first two tests pass and I believe them to correctly describe the favourites method in my router. The third test is the the one giving me problems. As I understand it, because I am testing my router and NOT the FavouriteMoviesView I should be stubbing out the view to keep the test isolated. If that is the correct assumption, my issue becomes that the stub won't have a renderFavourites method as it is a stubbed out Backbone.View().
How can I fix this particular problem and if you are so inclined, I believe I'm missing something conceptual so feel free to explain what it is that I'm not understanding.
Cheers.
You problem is that you want to mock something inside a mock function. What I would suggest is that instead of this...
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new Backbone.View());
...have this:
var StubView = Backbone.View.extend({
renderFavourites: sinon.stub()
});
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new StubView());
Now your View "constructor" will return a StubView, which has the method you are calling stubbed out. So this Backbone View with the stubbed out method will be placed in the router.favMoviesView -property. The favouritesViewStub property still contains just the "constructor" -function, so you can't access this stubbed method from there. This is why you haveto change this from the last test:
expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
to something like this:
expect(this.router.favMoviesView.renderFavourites).toHaveBeenCalledTwice();
This way you will actually check if the router's copy of the view has had the method called twice.
Hope this works for you, comment if it doesn't! I didn't test this out, so there could be some problems, but I'm sure the logic behind works.