How to unit test an angular 2 component when I don't care about the dependencies? I just want to test some inner functions - unit-testing

I'm new to writing unit tests, and unfortunately I've already built a few "complicated" (for me) components which I am having a hard time even beginning to write tests for.
Here's a snippet of my code, including the constructor. Basically, I don't really care right now about these dependencies, I want to test some inner functions such as resizing based on array size, etc. For these, I can just create an Array.fill and should be good to go.
export class GalleryComponent implements OnInit {
photos = [];
galleryState: Observable<any>;
constructor(
private store: Store<any>,
private photoActions: PhotoActions,
private router: Router
) {
this.galleryState = this.store.select<any>(state => state.photos.gallery);
}
}
In my other components which have nothing in the constructor, instantiating the component in my test is as simple as new SomeComponent().
However, in the GalleryComponent above, I am wondering if there is a way that I can literally ignore the dependencies completely (for now), and instead just instantiate the component in a way that I can test some inner functions easily. For example, say I had the following function inside GalleryComponent:
function timesByTwo(number) {
return number * 2;
}
This is not at all related to any of the dependencies, so how can I just test that one function given that this component has 3 dependencies?
Thanks

If you truly don't care about testing anything at all that is associated with your dependencies, then in your spec you can just construct your component with null values for those dependencies.
import { AppComponent } from './app.component';
describe('App: Test', () => {
let component: AppComponent;
beforeEach(() => {
component = new AppComponent(null, null, null);
});
it('should create the app', () => {
expect(component).toBeTruthy();
});
it(`Should return 4`, () => {
expect(component.timesByTwo(2)).toEqual(4);
});
}
To get around your current usage of this.store.select in your constructor you can modify your constructor like so
constructor(
private store: Store<any>,
private photoActions: PhotoActions,
private router: Router
) {
if(this.store == null){
this.galleryState = null;
}else{
this.galleryState = this.store.select<any>(state => state.photos.gallery);
}
}
Otherwise you can mock your Store component in your test page. An example
import { Observable } from 'rxjs/Rx'
export class MockService extends EventService{
constructor() { super(null); }
getEvents(user:string){
return Observable.of([{val: "test"}]);
}
}
and then modify my code from above to be
let component: AppComponent;
let mockService: MockService;
beforeEach(() => {
mockService = new MockService()
component = new AppComponent(null, mockService, null);
});

Related

Invalid syntax on EmberJS Octane observers

I'm trying to use Ember observers with EmberJS Octane latest version (4.1.0), but it does not seem to work.
Here is what I'm trying to achieve :
export default class SummaryService extends Service {
#service store;
#service authentication;
#reads('authentication.userId') userId;
#tracked weekPosts;
#tracked monthPosts;
#observer('userId')
loadPosts() {
this._loadPosts(this.userId);
}
_loadPosts(userId) {
this.store.query('posts', { filter: { 'current-week': 1, 'user-id': userId } })
.then((posts) => {
this.set('weekPosts', posts);
});
this.store.query('posts', { filter: { 'current-month': 1, 'user-id': userId } })
.then((posts) => {
this.set('monthPosts', posts);
});
}
}
=> The syntax is invalid.
I also tried :
#observer('userId', function() {
this._loadPosts();
});
=> The observer is indeed called, but this is undefined.
I also tried :
init() {
super.init(...arguments);
this.addObserver('currentUserId', this, '_loadPosts');
}
=> But this one does not call any method (even with inline method definition).
Finally, my last attempt was to use #computed properties for weekPosts and monthPosts instead, like this :
export default class SummaryService extends Service {
/* ... */
#computed('userId')
get weekPosts() {
return this.store.query('posts', { filter: { 'current-week': 1 } })
.then((posts) => { return posts; });
}
}
=> But it always returns a Promise, so I can't call .reduce on it from a computed property used by a Component :
export default class SummaryComponent extends Component {
#computed('weekPosts')
get weekPostsViewsCount() {
return this.weekPosts.reduce((sum, post) => { return sum + post.viewCount });
}
}
I finally got something working pretty ugly using an ArrayProxy.extend(PromiseProxyMixin) returned by the weekPosts computed property, but I'm definitely not happy with this for the following reasons :
So much code for such a simple thing
Everything (component, template) which uses the weekPosts has to make sure the promise is fulfilled before working with it
The promise is an implementation detail of the service and should not be visible in any way out of it
Thanks !
Observers won't work for what you want to do -- since it looks like you want to reactively re-fetch data (using ember-data) based on when userId changes, I have a library suggestion:
https://github.com/NullVoxPopuli/ember-data-resources
With this library, we can replace most of your service with this:
import { query } from 'ember-data-resources';
export default class SummaryService extends Service {
#service authentication;
#reads('authentication.userId') userId;
_weekPosts = query(this, 'posts', () => ({
filter: { 'current-week': 1, 'user-id': this.userId
}));
_monthPosts = query(this, 'posts', () => ({
filter: { 'current-month': 1, 'user-id': this.userId
}));
get weekPosts() {
return this._weekPosts.records ?? [];
}
get monthPosts() {
return this._monthPosts.records ?? [];
}
get isLoading() {
return this._weekPosts.isLoading || this._monthPosts.isLoading;
}
}
The advantage here is that you also have the ability to manage error/loading/etc states.
This uses a technique / pattern called "Derived state", where instead of performing actions, or reacting to changes, or interacting withe lifecycles, you instead define how data is derived from other data.
In this case, we have known data, the userId, and we want to derive queries, using query from ember-data-resources, also uses derived state to provide the following api:
this._weekPosts
.records
.error
.isLoading
.isSuccess
.isError
.hasRun
Which then allows you to define other getters which derive data, weekPosts, isLoading, etc.
Derived state is much easier to debug than observer code -- and it's lazy, so if you don't access data/getters/etc, that data is not calculated.

Jest -- Mock a function called inside a React Component

Jest provides a way to mock functions as described in their docs
apiGetMethod = jest.fn().mockImplementation(
new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(
() => users[userID] ? resolve(users[userID]) : reject({
error: 'User with ' + userID + ' not found.',
});
);
});
);
However these mocks only seem to work when the function is called directly in a test.
describe('example test', () => {
it('uses the mocked function', () => {
apiGetMethod().then(...);
});
});
If I have a React Component defined as such how can I mock it?
import { apiGetMethod } from './api';
class Foo extends React.Component {
state = {
data: []
}
makeRequest = () => {
apiGetMethod().then(result => {
this.setState({data: result});
});
};
componentDidMount() {
this.makeRequest();
}
render() {
return (
<ul>
{ this.state.data.map((data) => <li>{data}</li>) }
</ul>
)
}
}
I have no idea how to make it so Foo component calls my mocked apiGetMethod() implementation so that I can test that it renders properly with data.
(this is a simplified, contrived example for the sake of understanding how to mock functions called inside react components)
edit: api.js file for clarity
// api.js
import 'whatwg-fetch';
export function apiGetMethod() {
return fetch(url, {...});
}
You have to mock the ./api module like this and import it so you can set the implemenation of the mock
import { apiGetMethod } from './api'
jest.mock('./api', () => ({ apiGetMethod: jest.fn() }))
in your test can set how the mock should work using mockImplementation:
apiGetMethod.mockImplementation(() => Promise.resolve('test1234'))
In case the jest.mock method from #Andreas's answer did not work for you. you could try the following in your test file.
const api = require('./api');
api.apiGetMethod = jest.fn(/* Add custom implementation here.*/);
This should execute your mocked version of the apiGetMethod inside you Foo component.
Here is an updated solution for anyone struggling with this in '21. This solution uses Typescript, so be aware of that. For regular JS just take out the type calls wherever you see them.
You import the function inside your test at the top
import functionToMock from '../api'
Then you indeed mock the call to the folder outside of the tests, to indicate that anything being called from this folder should and will be mocked
[imports are up here]
jest.mock('../api');
[tests are down here]
Next we mock the actual function we're importing. Personally I did this inside the test, but I assume it works just as well outside the test or inside a beforeEach
(functionToMock as jest.Mock).mockResolvedValue(data_that_is_returned);
Now here's the kicker and where everyone seems to get stuck. So far this is correct, but we are missing one important bit when mocking functions inside a component: act. You can read more on it here but essentially we want to wrap our render inside this act. React testing library has it's own version of act. It is also asynchronous, so you have to make sure your test is async and also define the destructured variables from render outside of it.
In the end your test file should look something like this:
import { render, act } from '#testing-library/react';
import UserGrid from '../components/Users/UserGrid';
import { data2 } from '../__fixtures__/data';
import functionToMock from '../api';
jest.mock('../api');
describe("Test Suite", () => {
it('Renders', async () => {
(functionToMock as jest.Mock).mockResolvedValue(data2);
let getAllByTestId: any;
let getByTestId: any;
await act(async () => {
({ getByTestId, getAllByTestId } = render(<UserGrid />));
});
const container = getByTestId('grid-container');
const userBoxes = getAllByTestId('user-box');
});
});
Another solution to mock this would be:
window['getData'] = jest.fn();

How can I test computed properties in VueJS?

I'm using VueJS from Vue CLI. So all my components are in .vue format.
In one of my components, I have an array called fields in the data section.
//Component.vue
data() {
return {
fields : [{"name" : "foo", "title" : "Foosteria"}, {"name" : "bar", "title" : "Barrista"}]
}
}
I have a computed property that is a subset of fields
//Component.vue
computed : {
subsetOfFields () {
// Something else in component data determines this list
}
}
I've set up all of my unit tests in jasmine like this and they work fine.
//Component.spec.js
import Vue from 'vue'
import MyComponent from 'Component.vue'
describe("Component test", function() {
var myComponentVar = new Vue(MyComponent);
var vm = myComponentVar.$mount();
beforeEach(function() {
vm = myComponentVar.$mount();
);
afterEach(function() {
vm = myComponentVar.$destroy();
});
it("First spec tests something", function() {
...
});
});
For everything else, doing something inside the spec, then running assertions on the data objects works just fine. However, running an assertion on subsetOfFields always returns an empty array. Why so? What should I do, in order to be able to test it?
FYI, I even tried nesting the spec inside another describe block and then adding a beforeEach which initializes the fields array. It did not work.
However, initializing fields inside the generic beforeEach function worked. But I don't want to initialize the fields array with that mock data for the other specs.
I came across this link that talks about testing and the section you'll need to look at is the Vue.nextTick(...) section
https://alligator.io/vuejs/unit-testing-karma-mocha/
The block I'm talking about is below:
import Vue from 'vue';
// The path is relative to the project root.
import TestMe2 from 'src/components/TestMe2';
describe('TestMe2.vue', () => {
...
it(`should update when dataText is changed.`, done => {
const Constructor = Vue.extend(TestMe2);
const comp = new Constructor().$mount();
comp.dataProp = 'New Text';
Vue.nextTick(() => {
expect(comp.$el.textContent)
.to.equal('New Text');
// Since we're doing this asynchronously, we need to call done() to tell Mocha that we've finished the test.
done();
});
});
});

Get an instance of TypeScript class?

I have some legacy code I want to start unit-testing. It's a class like this:
export class Controller {
private something: any;
constructor() { this.something = true; }
public getSomething(): any { return this.something; }
}
Trying to unit-test it with Mocha like this:
import Controller from '../../src/Controller';
describe('Controller', () => {
let subject: any;
beforeEach( () => {
subject = new Controller(); // compiler complains here
});
describe('getOptions()', () => {
it('should get something', () => {
let result: any = subject.getOptions();
if (typeof result !== 'object') {
throw new Error('Expected object but got ' + result);
}
});
});
});
Compiler complains:
[ts] Cannot use 'new' with an expression whose type lacks a call or
construct signature.
How do I get an instance of the Controller class to run tests against?
It doesn't work because you are trying to import the Controller as a default export while it isn't.
Try:
import {Controller} from '../../src/Controller';
or:
export default class Controller {
You can read more about imports in general on the MDN page here.

Angular2. How to unit test a component with dynamically created html

I have created a virtual list component, where I only render the visible lines.
When creating unit tests, I can test the view while adding elements, but when I remove or change elements then the 'fixture.debugElement' still returns the previous count.
I have created this small test component which shows the problem
The component
class TestComponent {
#ViewChild('view') view: ElementRef;
constructor(public ngRenderer: Renderer) {
}
public add(text: string): void {
let parentelm = this.view.nativeElement;
let element = this.ngRenderer.createElement(parentelm, 'div');
this.ngRenderer.setText(element, text);
}
public remove(index: number): void {
let elm: HTMLElement = this.view.nativeElement;
let child = elm.children[index];
elm.removeChild(child);
}
}
and the test
describe('test component', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
});
it('add and remove divs', () => {
component.add('item1');
component.add('item2');
let content = fixture.debugElement.query( (elm: DebugElement) => { return elm.attributes['id'] === 'list'; });
expect(content.children.length).toBe(2);
component.add('item3');
expect(content.children.length).toBe(3); // <-- this works
component.remove(1);
fixture.detectChanges();
expect(content.children.length).toBe(2); // <-- this fails
});
});
When checking the browser, then of course the view only contains 'item1' and 'item3'
How can I force an update of the debug element ?
Edit:
I see the fixture contains the native element.
If I add this
let elm = <HTMLElement>fixture.elementRef.nativeElement;
console.log(elm.innerHTML);
Then I see the correct html
'<div id="list"><div>item1</div><div>item3</div></div>'
So maybe the solution is to iterate native elements instead of using the debugElement ?