Angular 2 Testing: Spying on ngOnInit() in beforeEach? - javascript

Context
My issue is that my ngOnInit function calls many sub functions asynchronously by using Firestore to populate a form from one document as well as subscribing to various collections to display lists of data. This gets very tedious to test as I then have to ensure things are subscribing properly and values are being returned in the correct order.
According to the Angular docs:
However, it's often more productive to explore the inner logic of application classes with isolated unit tests that don't depend upon Angular. Such tests are often smaller and easier to read, write, and maintain.
Which in my opinion would make preventing the ngOnInit (through a spy) a valid way of
Isolating tests and
Reducing complexity when initializing the tests
You can then explicitly call each function separately and only test what is needed, instead of having ngOnInit calling the functions (and possible you as well).
This is exactly how I have gone about it so far:
Component
ngOnInit() {
console.log('init');
this.setup();
}
setup() {
this.firebaseService.setup(_ => {
this.checkNewOrEdit();
});
}
...
Spec
...
beforeEach(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
spyOn(component, 'ngOnInit');
fixture.detectChanges();
});
it('should be created', () => {
fixture.whenStable().then(_ => {
expect(component).toBeTruthy();
});
});
it('should call checkNewOrEdit() on setup()',
() => {
spyOn(component, 'checkNewOrEdit');
callbackValue = 'success';
component.setup();
expect(component.checkNewOrEdit).toHaveBeenCalled();
}
);
Which is working fine for me so far.
Is this an acceptable solution? Or are there better ways of going about this?

I had problems further down the line in terms of variables not being set previously.
This solution does work but it just adds further complexity further down the line. I would recommend solving the problem initially, then you don't have to worry about how and where variables where set later
I solved my problems by editing my firebase mocks to include all the collections and docs I subscribe to throughout the initialisation chain. I think I must have forgotten a a doc or collection somewhere.

Related

[vue-test-utils]: overwriting methods via the `methods` property or setMethods is deprecated

I am trying to test the methods of my Vue components; the test is seems working fine. But the problem is that it's giving a depreciation warning in the console.
I believe that the vue-test-utils teams would be removing the setMethods and methods property in their next major release.
My problem is that there are no alternative ways to achieve the same things provided for both setMethods and methods property.
Just they suggested a warning:
To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
My Question: How can we extract the method and test the functionalities of the method clicked from the component level or not?
Below is my simple example where I mocked a method and checking if it gets called inside the component when triggered click.
const downloadStub = jest.fn()
const wrapper = mount(Documents, {
methods: { downloadNow: downloadStub },
})
it('check download button clicked and calling downloadNow method', () => {
wrapper.find('.download-button').trigger('click')
expect(downloadStub).toBeCalled()
})
Note: No issues in running the above code; I want to know the alternative ways to achieve the same result and avoid the warning?
After some research, I found one solution that works the way I expected. I am able to achieve this, without using any methods properties and setMethods.
This way, I got rid of the warning message methods property is deprecated and will be removed.
const wrapper = mount(Documents, {
})
it('check download button clicked and calling downloadNow method', () => {
const downloadSpy = jest.spyOn(wrapper.vm, 'downloadNow')
wrapper.find('.download-button').trigger('click')
expect(downloadSpy).toBeCalled()
})
I use the jest.spyOn method and pass the wrapper.vm and method name.
Other solutions are also welcome.

How to create and call selectors from a separate file in Cypress?

I am new to Cypress and struggling to get it work. I need to create a file with selectors (I suppose in 'support' folder) to use them in my project file.
Here is an example
describe('Test_spec_1', () => {
it.only('Visits the site & verifies elements', () => {
cy.get('[type=text]').should('be.visible')
cy.get('[type=password]').should('be.visible')
cy.get('[type=submit]').should('be.visible')
cy.get('[routerlink="/login"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
cy.get('[routerlink="/support"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
})
})
Basically, I need to have all selectors in a separate file, so I can call them and update their values easily.
I have experimented a bit with export/import, but it did not work. Could't find anywhere how to use it properly. It would be great if you can give me some hints how to do it. Thank you very much.
You can create a folder called page-objects inside your integration folder. Inside that you can create one js file for each screen like login.js. Now inside that you can write your locators like:
class login {
usernameInput() {
return cy.get('[type=text]')
}
passwordInput() {
return cy.get('[type=password]')
}
submitButton() {
return cy.get('[type=submit]')
}
}
export default login
Inside your tests you can use them as:
import login from '/page-objects/login.js'
const loginPage = new login();
describe('Test_spec_1', function() {
it('Visits the site & verifies elements', function() {
loginPage.usernameInput().should('be.visible')
loginPage.passwordInput().should('be.visible')
loginPage.submitButton().should('be.visible')
})
})
Please don't use page objects in Cypress, see this tutorial Stop using Page Objects
Page objects problems
Page objects are hard to maintain and take away time from actual application development. I have never seen PageObjects documented well enough to actually help one write tests.
Page objects introduce additional state into the tests, which is separate from the application’s internal state. This makes understanding the tests and failures harder.
Page objects try to fit multiple cases into a uniform interface, falling back to conditional logic - a huge anti-pattern in our opinion.
Page objects make tests slow because they force the tests to always go through the application user interface.
Number 3) killed it for me. You get to a point where you try to figure out more convoluted methods in the page object to cater for different scenarios.
The easiest way to store your selector text in one place is given in this question Where to store selectors in Cypress.io
// cypress/support/selectors.js
export default {
mySelector: '.my-selector',
mySelector2: '.my-selector-2'
};
// cypress/integration/one.spec.js
import selectors from '../support/selectors.js';
describe('test', () => {
it('test', () => {
cy.get(selectors.mySelector);
});
});

How to deal with race conditions in event listeners and shared state?

I have 2 event listeners that operate on the same shared data/state. For instance:
let sharedState = {
username: 'Bob',
isOnline: false,
};
emitter.on('friendStatus', (status) => {
sharedState.isOnline = status.isOnline;
});
emitter.on('friendData', (friend) => {
if (sharedState.isOnline) {
sharedState.username = friend.username;
}
});
My problem is that these events are emitted at any order. The friendData event might come in before the friendStatus. But friendData does something with the data returned from friendStatus. In other words: I need the event handler for friendData to execute after friendStatus, but I don't have this assurance from the event emitter perspective. I need to somehow implement this in my code.
Now of course I could simply remove the if (sharedState.isOnline) { from the friendData listener and let it run its course. Then I'd have a function run after both handlers have finished and somewhat reconciliate the shared state dependencies:
emitter.on('friendStatus', (status) => {
sharedState.isOnline = status.isOnline;
reconcileStateBetweenUsernameAndIsOnline();
});
emitter.on('friendData', (friend) => {
sharedState.username = friend.username;
reconcileStateBetweenUsernameAndIsOnline();
});
Problem is that this reconciliation function knows about this specific data dependencies use case; hence cannot be very generic. With large interconnected data dependencies this seems a lot harder to achieve. For instance I am already dealing with other subscriptions and other data dependencies and my reconciliation function is becoming quite large and complicated.
My question is: is there a better way to model this? For instance if I had the assurance that the handlers would run in a specific order I wouldn't have this issue.
EDIT: expected behavior is to use the sharedState and render a UI where I want the username to show ONLY if the status isOnline is true.
From #Bergi's answer in the comments the solution I was hinting seems to be the most appropriate for such case. Simply let the event-handlers set their own independent state, then observe on the values changing and write appropriate logic based on what you need to do. For instance I need to show a username; this function shouldn't care about the order or have any knowledge of time: it should simply check whether the isOnline status is true and if there's a username. Then the observable pattern can be used to call this function whenever each dependency of the function changes. In this case the function depends on status.isOnline and friend.username hence it will observe and re-execute whenever those values change.
function showUsername() {
if (status.isOnline && friend.username != '') return true;
}
This function must observe the properties it depends on (status.isOnline and friend.username). You can have a look at RxJS or other libraries for achieving this in a more "standard" way.

Should I test Vue.js private functions?

So after I read common tips and watched the video about testing provided by Vue.js I decided to test only behaviors of my components. But I maybe misunderstand the concept. Here is the component:
<template>
<es-btn
class="a"
round
outline
color="primary"
#click="skipScan"
>
Skip Upload
</es-btn>
</template>
<script>
export default {
data: () => ({
uploadScanningDialog: false,
}),
methods: {
// public functions here which will be tested
skipScan() {
hideDialog();
triggerSkipScan();
},
},
}
// private functions here as vue.js suggests
function hideDialog() {
this.uploadScanningDialog = false;
}
....
</script>
I want to test the behavior:
it('should hide itself and show next dialog when Scan Button is clicked', () => {
const data = {
uploadScanningDialog: true,
};
wrapper.setData(data);
expect(wrapper.vm.uploadScanningDialog).toBeTruthy();
wrapper.find('.a').trigger('click');
expect(wrapper.vm.uploadScanningDialog).toBeFalsy();
});
So here are the questions and errors:
Should I do test this way, by triggering the action instead of calling the method itself? Because I used to test by calling the method and expecting some results, not triggering the component who calls the method
Shouldn't I test the private functions? Because my test tries to call real method, so the test I share gets error. I will mock them but I'm not sure how to continue
Generally it is better to unit test your code through its public api, where possible.
This improves maintainability, as:
It allows you to refactor any private functions, without having to update your unit tests.
If you make changes to your private functions, you can then assert that you haven't broken anything by running your unit tests against your public api.
Whether you run your unit tests by calling a public method or triggering a DOM event is a matter of choice.
The latter gives greater test coverage as you are also testing if you have wired up the event to the event handler function correctly in your template; but it is slightly less maintainable, as if you change your template you may have to update your tests.

How to mock an asynchronous function call in another class

I have the following (simplified) React component.
class SalesView extends Component<{}, State> {
state: State = {
salesData: null
};
componentDidMount() {
this.fetchSalesData();
}
render() {
if (this.state.salesData) {
return <SalesChart salesData={this.state.salesData} />;
} else {
return <p>Loading</p>;
}
}
async fetchSalesData() {
let data = await new SalesService().fetchSalesData();
this.setState({ salesData: data });
}
}
When mounting, I fetch data from an API, which I have abstracted away in a class called SalesService. This class I want to mock, and for the method fetchSalesData I want to specify the return data (in a promise).
This is more or less how I want my test case to look like:
predefine test data
import SalesView
mock SalesService
setup mockSalesService to return a promise that returns the predefined test data when resolved
create the component
await
check snapshot
Testing the looks of SalesChart is not part of this question, I hope to solve that using Enzyme. I have been trying dozens of things to mock this asynchronous call, but I cannot seem to get this mocked properly. I have found the following examples of Jest mocking online, but they do not seem to cover this basic usage.
Hackernoon: Does not use asychronous calls
Wehkamp tech blog: Does not use asynchronous calls
Agatha Krzywda: Does not use asynchronous calls
GitConnected: Does not use a class with a function to mock
Jest tutorial An Async Example: Does not use a class with a function to mock
Jest tutorial Testing Asynchronous Code: Does not use a class with a function to mock
SO question 43749845: I can't connect the mock to the real implementation in this way
42638889: Is using dependency injection, I am not
46718663: Is not showing how the actual mock Class is implemented
My questions are:
How should the mock class look like?
Where should I place this mock class?
How should I import this mock class?
How do I tell that this mock class replaces the real class?
How do set up the mock implementation of a specific function of the mock class?
How do I wait in the test case for the promise to be resolved?
One example that I have that does not work is given below. The test runner crashes with the error throw err; and the last line in the stack trace is at process._tickCallback (internal/process/next_tick.js:188:7)
# __tests__/SalesView-test.js
import React from 'react';
import SalesView from '../SalesView';
jest.mock('../SalesService');
const salesServiceMock = require('../SalesService').default;
const weekTestData = [];
test('SalesView shows chart after SalesService returns data', async () => {
salesServiceMock.fetchSalesData.mockImplementation(() => {
console.log('Mock is called');
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
});
const wrapper = await shallow(<SalesView/>);
expect(wrapper).toMatchSnapshot();
});
Sometimes, when a test is hard to write, it is trying to tell us that we have a design problem.
I think a small refactor could make things a lot easier - make SalesService a collaborator instead of an internal.
By that I mean, instead of calling new SalesService() inside your component, accept the sales service as a prop by the calling code. If you do that, then the calling code can also be your test, in which case all you need to do is mock the SalesService itself, and return whatever you want (using sinon or any other mocking library, or even just creating a hand rolled stub).
You could potentially abstract the new keyword away using a SalesService.create() method, then use jest.spyOn(object, methodName) to mock the implementation.
import SalesService from '../SalesService ';
test('SalesView shows chart after SalesService returns data', async () => {
const mockSalesService = {
fetchSalesData: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
})
};
const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService);
const wrapper = await shallow(<SalesView />);
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalled();
expect(mockSalesService.fetchSalesData).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
One "ugly" way I've used in the past is to do a sort of poor-man's dependency injection.
It's based on the fact that you might not really want to go about instantiating SalesService every time you need it, but rather you want to hold a single instance per application, which everybody uses. In my case, SalesService required some initial configuration which I didn't want to repeat every time.[1]
So what I did was have a services.ts file which looks like this:
/// In services.ts
let salesService: SalesService|null = null;
export function setSalesService(s: SalesService) {
salesService = s;
}
export function getSalesService() {
if(salesService == null) throw new Error('Bad stuff');
return salesService;
}
Then, in my application's index.tsx or some similar place I'd have:
/// In index.tsx
// initialize stuff
const salesService = new SalesService(/* initialization parameters */)
services.setSalesService(salesService);
// other initialization, including calls to React.render etc.
In the components you can then just use getSalesService to get a reference to the one SalesService instance per application.
When it comes time to test, you just need to do some setup in your mocha (or whatever) before or beforeEach handlers to call setSalesService with a mock object.
Now, ideally, you'd want to pass in SalesService as a prop to your component, because it is an input to it, and by using getSalesService you're hiding this dependency and possibly causing you grief down the road. But if you need it in a very nested component, or if you're using a router or somesuch, it's becomes quite unwieldy to pass it as a prop.
You might also get away with using something like context, to keep everything inside React as it were.
The "ideal" solution for this would be something like dependency injection, but that's not an option with React AFAIK.
[1] It can also help in providing a single point for serializing remote-service calls, which might be needed at some point.

Categories

Resources