I'm currently getting an error that says "can't read property 'get' of null" which is because I'm passing in null as my first argument to the FirmService constructor in my beforeEach in my spec file...what is the best way to mock or pass in http to my service here?
#Injectable
export class FirmService {
public stateObservable: Observable<FirmState>;
constructor(private: $http: AuthHttp, private store: Store<FirmState>) {
this.stateObservable = this.store.select('firmReducer');
}
public getFirms(value?: string) {
return this.$http.get('/api/firm').map((response: Response) => {
this.store.dispatch({
type: firmActions.GET_FIRMS,
payload: response.json()
});
return;
}
}
}
Here is my unit test for the above service:
import {Store} from '#ngrx/store';
import {FirmService} from './firm.service'
import {firmActions} from './firm.reducer'
import {FirmState} from './firm.state'
import {HttpModule, Http, Response, ResponseOptions, XHRBackend} from 'angular/http';
import {MockBackend, MockConnection} from '#angular/http/testing';
class MockStore extends Store<FirmState> {
constructor() {
super(null, null, null)
}
public dispatch () {
return undefined;
}
}
describe('firm actions', () => {
it('getFirms should dispatch the GET_FIRMS action', () => {
let connection: MockConnection;
const expectedAction = {
type: firmActions.GET_FIRMS
payload: undefined
}
const mockBackendResponse = (connection: MockConnection, response: string) => {
connection.mockRespond(new Response(new ResponseOptions({ body: response })));
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
{provide: XHRBackend, useClass: MockBackend}
]
});
spyOn(mockStore, 'dispatch');
firmService.getFirms().subscribe(result => {
expect(mockStore.dispatch).toHaveBeenCalled();
expect(mockStore.dispatch).toHaveBeenCalledWith(expectedAction);
};
}
}
}
you can try something using MockBackend and MockConnection from angular's http/testing library:
import { ResponseOptions, Response, XHRBackend, HttpModule } from '#angular/http';
import { MockBackend, MockConnection } from '#angular/http/testing';
const mockBackendResponse = (connection: MockConnection, response: string) => {
connection.mockRespond(new Response(new ResponseOptions({ body: response })));
};
// test module configuration for each test
const testModuleConfig = () => {
TestBed.configureTestingModule({
imports: [
//.. your required modules for this test,
HttpModule, RouterTestingModule
],
providers: [
// required services,
{ provide: XHRBackend, useClass: MockBackend }
]
});
};
then before each test:
beforeEach(() => {
injector = getTestBed();
backend = <any>injector.get(XHRBackend);
store = injector.get(Store);
// sets the connection when someone tries to access the backend with an xhr request
backend.connections.subscribe((c: MockConnection) => connection = c);
// construct after setting up connections above
firmService = injector.get(FirmService);
});
sample test using an Array of Items as result:
t.it('should search', () => {
let list: Array<Item> = []; // ... your sample mock entity with fields
observer.subscribe(result => {
expect(result).toEqual(new SearchedAction(list));
});
// mock response after the xhr request (which happens in constructor), otherwise it will be undefined
let expectedJSON:string = JSON.stringify(list);
mockBackendResponse(connection, expectedJSON);
}
Related
So, when i get the info from the MongoDB database in a NestJS service, i want to return it to the controller and send it to the client (Angular)
I tried returning it with a Promise but i cant get it working.
Heres my code with the thing a tried:
Service:
import { Injectable } from "#nestjs/common";
import { InjectModel } from "#nestjs/mongoose";
import mongoose, { Model } from "mongoose";
import {
FriendRequest,
FriendRequestDocument,
} from "../schema/friend-requests.schema";
import { Users, UsersDocument } from "../schema/user.schema";
import { FriendsDto } from "./dto/friends.dto";
#Injectable()
export class AddFriendService {
constructor(
#InjectModel(Users.name) private readonly usersModel: Model<UsersDocument>,
#InjectModel("friendRequests")
private readonly friendrequestModel: Model<FriendRequestDocument>
) {}
async getRequests(userid: string) {
let incomingrqs = new Promise((resolve, reject) => {
let response = {
incoming: [],
};
this.friendrequestModel
.aggregate([
{
$lookup: {
from: "users",
localField: "author",
foreignField: "_id",
as: "userdata",
// pipeline: [
// {
// $match: {
// friend_id: new mongoose.Types.ObjectId(userid)
// }
// }
// ],
},
},
])
.exec((err, data) => {
if (err) {
console.log(err);
}
if (!data) {
console.log("No data returned");
}
data.forEach((rqsData) => {
response.incoming.push({
userid: rqsData.userdata[0]._id,
username: rqsData.userdata[0].username,
created_at: rqsData.userdata[0].created_at,
pfp: "/assets/cdn/pfp/" + rqsData.userdata[0].pfp,
});
});
});
resolve(response);
})
incomingrqs.then(x => {
return x;
})
}
}
Controller:
import { Get, Controller, Body, Post, Param } from "#nestjs/common";
import { AddFriendService } from "./friends.service";
import { FriendsDto } from "./dto/friends.dto";
#Controller("friends")
export class AddFriendController {
constructor(private friendsService: AddFriendService) {}
#Post("rqs")
async getRqs(#Body() friendsDto: FriendsDto){
let rqs = await this.friendsService.getRequests(friendsDto.userid);
console.log(rqs)
return rqs;
}
}
FriendRequestDocument:
import { Prop, Schema, SchemaFactory } from "#nestjs/mongoose";
import mongoose, { Document, ObjectId } from "mongoose";
export type FriendRequestDocument = FriendRequest & Document;
#Schema({collection: "friendRequests"})
export class FriendRequest {
#Prop()
author: mongoose.Types.ObjectId;
#Prop()
friend_id: mongoose.Types.ObjectId;
#Prop()
request_at: Date;
}
export const FriendRequestSchema = SchemaFactory.createForClass(FriendRequest);
Can you help me? Thanks!
aggregate.exec can already return a promise if no callback is given, just FYI.
You need to return the incomingrqs to the controller. This is standard of how promises work.. Returning inside the then forwards the return on to the next promise, which is why the incomingrqs needs to be returned as well. Otherwise, the controller will never know what the service returns.
I'm having below setup in my project, whenever I extends the httpService and use the 'this.instance' in any service I'm getting the error.
If I use axios.get directly without any interceptors in my service files its working fine.
Im new to the unit testing and badly stuck with this. Please share your comments below. It'll be really helpful.
httpClient.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { DEFAULT_HEADERS, HOST } from './ApiHelper';
export abstract class HttpService {
protected readonly instance: AxiosInstance;
public constructor(requestConfig: AxiosRequestConfig) {
this.instance = axios.create(requestConfig);
this.instance.interceptors.request.use((request) => {
request.baseURL = HOST;
request.headers = { ...DEFAULT_HEADERS };
return request;
});
this.instance.interceptors.response.use(
(response) =>
response,
(error) =>
new Promise((resolve, reject) => {
reject(error.response);
}),
);
}
}
export default HttpService;
someService.ts
import HttpService from './HttpService';
const warningListUrl = 'some/url';
class SomeService extends HttpService {
public constructor() {
super({
});
}
public async getSomething(params: any) {
this.instance({
method: 'GET',
url: warningListUrl,
params,
}).then((res) =>
res.data);
}
}
export default SomeService;
ReactComponent.tsx
const fetchList = async () => {
try {
setIsLoading(true);
const someService = new SomeService();
const response: any = await someService.getSomething({});
setWarnings(response.content);
setTotalPages(response.totalPages);
} catch (error) {
console.log(error);
} finally { setIsLoading(false); }
};
useEffect(() => {
fetchList();
}, []);
ReactComponent.test.tsx
jest.mock('../../services/SomeService');
const someService = new SomeService();
describe('page tests', () => {
test('page renders without crashing', async () => {
(someService.getWarningList as jest.Mock).mockResolvedValue(someMatchingData);
await act(async () => {
render(<ReactComponent />);
});
const text = screen.getByText('Warning 1');
expect(text).toBeInTheDocument();
});
}
Error:
TestingLibraryElementError: Unable to find an element with the text: Warning 1. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
render(<Warning />);
});
-> const text = screen.getByText('Warning 1');
expect(text).toBeInTheDocument();
});
You could use requireActual if you need to mock only specific methods.
jest.mock('../../services/SomeService', ()=> {
return {
...jest.requireActual('../../services/SomeService'),
getWarningList: new Promise.resolve(someMatchingData)
}
})
How about mocking a module like this?
Accessing methods through 'prototype' saved my day.
(someService.prototype.getWarningList as jest.Mock).mockResolvedValue(someMatchingData);
just adding it above the test description saved me.
Array not passing from service to component:
In the test() function on the service.ts page, google calendar data is successfully being read and pushed to an array called response. All the data logs.
When lesson-summary.component.ts calls on the test() function, the response array data does not show up in the lesson-summary.component.html
Thanks for any help!
google-calendar.service.ts
import { Injectable, Directive } from "#angular/core";
import * as moment from "moment-timezone";
declare var gapi: any;
#Injectable({
providedIn: "root"
})
export class GoogleCalendarService {
private response = [];
constructor() { }
test() {
gapi.load("client", () => {
gapi.client.init({
apiKey: "API string here",
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"]
}).then(() => {
var month = moment().month();
const firstOfMonth = moment().startOf("month").format("YYYY-MM-DD hh:mm");
const lastOfMonth = moment().endOf("month").format("YYYY-MM-DD hh:mm");
var firstOfMonthUTC = moment.tz(firstOfMonth, "America/Toronto").format();
var lastOfMonthUTC = moment.tz(lastOfMonth, "America/Toronto").format();
return gapi.client.calendar.events.list({
calendarId: "calendar id here",
timeMax: lastOfMonthUTC,
timeMin: firstOfMonthUTC,
singleEvents: true
});
})//end of .then
.then((data) => {
this.response.push.apply(this.response, data.result.items);
console.log(data.result.items, "data.result.items");
return this.response;
});//end of .then
});//end of .load
}//end of test
}//end of export
lesson-summary.component.ts
import { Component, OnInit } from "#angular/core";
import { Observable } from "rxjs";
import { GoogleCalendarService } from "../google-calendar.service";
declare var gapi: any;
#Component({
selector: "app-lesson-summary",
templateUrl: "./lesson-summary.component.html",
styleUrls: ["./lesson-summary.component.css"]
})
export class LessonSummaryComponent implements OnInit {
private response;
constructor(
private calendarService: GoogleCalendarService) {
this.response = this.calendarService.test();
}
ngOnInit() {
}
}
lesson-summary.component.html
<ul>
<li *ngFor = "let item of response">
{{ item.summary }}
</li>
</ul>
That's because you're mixing promises and sync functions in an incorrect way, so the test() function will not return anything.
Try adding a promise to your test():
test() {
return new Promise(resolve => { // <-- now test func return promise
gapi.load("client", () => {
gapi.client.init({
apiKey: "API string here",
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"]
}).then(() => {
// code...
}).then((data) => {
// code...
resolve(this.response); // <-- when we have the response, we are resolving the promise
});
});
});
}
And then use this promise in the component:
this.calendarService.test().then(data => this.response = data);
Learn more about promises on MDN
I'm new to the test part of a project.
I got a problem in my personal project. I use 'superagent' to get the information from Api and now I want to write test for it. But I cannot use 'fetch-mock' package which used in the Enzyme example.
Here is my action file.
// getRecommendedProductsActions.js
import request from 'superagent';
export const getRecommendedProducts = () => (dispatch) => {
dispatch(fetchProducts());
return request
.get(URL_PRODUCT_BASE)
.set('Content-Type', 'application/json')
.then(res => dispatch(receiveProducts(res.body)))
.catch(err => dispatch(receiveFailure(err)));
};
Here is my test file.
// test/getRecommendedProducts.test.js
import configureMockStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import { getRecommendedProducts } from '../../src/actions/products';
describe('async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('creates RECEIVE_PRODUCTS when fetching products has been done', () => {
fetchMock
.get('/products', {
body: httpBody,
headers: { 'content-type': 'application/json' },
});
const expectedActions = successResponse;
const store = mockStore();
return store.dispatch(getRecommendedProducts())
.then(() => expect(store.getActions()).toEqual(expectedActions));
});
And I find that 'superagent' is not fetch based and 'fetch-mock' doesn't work.
I also find a __mocks__/superagent.js file.
// mock for superagent - __mocks__/superagent.js
let mockDelay;
let mockError;
let mockResponse = {
status() {
return 200;
},
ok() {
return true;
},
body: {
walla: true,
},
get: jest.fn(),
toError: jest.fn(),
};
const Request = {
post() {
return this;
},
get() {
return this;
},
send() {
return this;
},
query() {
return this;
},
field() {
return this;
},
set() {
return this;
},
accept() {
return this;
},
timeout() {
return this;
},
end: jest.fn().mockImplementation(function (callback) {
if (mockDelay) {
this.delayTimer = setTimeout(callback, 0, mockError, mockResponse);
return;
}
callback(mockError, mockResponse);
}),
// expose helper methods for tests to set
__setMockDelay(boolValue) {
mockDelay = boolValue;
},
__setMockResponse(mockRes) {
mockResponse = mockRes;
},
__setMockError(mockErr) {
mockError = mockErr;
},
};
module.exports = Request;
Thanks for all help from you guys.
I want to develop a file configuration of json and it is called with http get the constructor and return the value I want the config file to another component. But when return gives me value undefined.
My Config.json
[ {"urlServer": "http://localhost:56877"}]
My Config.Service
export class configService
{
url: string;
constructor(public _http: Http)
{
let injector = Injector.resolveAndCreate([loggerService]);
let logger = injector.get(loggerService);
try {
return this._http.get('/app/config.json',
{
headers: contentHeaders
})
.map((res: any) =>
{
let data = <configModel>res.json();
this.url = data.urlServer;
JSON.stringify(this.url);
});
}
catch (ex) {
logger.registarErros('configService', ex);
}
}
returnConfig()
{
return this.url;
}
Now my other Component
constructor(public _http: Http, public config: configService)
{
this.token = sessionStorage.getItem('token');
this.username = sessionStorage.getItem('username');
}
login(username: String, password: String)
{
let injector = Injector.resolveAndCreate([loggerService]);
let logger = injector.get(loggerService);
try
{
alert(this.config.url);
return this._http.post('http://localhost:56877/api/Login/EfectuaLogin', JSON.stringify({ username, password }),
{
headers: contentHeaders
})
.map((res: any) =>
{
let data = <authLoginModel>res.json();
this.token = data.token;
this.username = data.nome;
sessionStorage.setItem('token', this.token);
sessionStorage.setItem('username', this.username);
return Observable.of('authObservable');
});
}
catch (ex) {
logger.registarErros('authentication', ex);
}
}
I no longer know how to solve the problem, I need your help, I'm not very experienced with Angular 2.
Thanks very much.
The problem here is that the config is load asynchronously. You could use something like that leveraging the flatMap operator:
#Injectable()
export class ConfigService {
urlServer:string;
constructor(public _http: Http) {
}
getConfig() {
if (this.urlServer) {
return Observable.of(this.urlServer);
}
return this._http.get('/app/config.json', {
headers: contentHeaders
})
.map((res: any) => {
let data = <configModel>res.json();
return data.urlServer;
}).do(urlServer => {
this.urlServer = urlServer;
});
}
}
and in your component:
login(username: String, password: String) {
return this.configService.getConfig().flatMap(urlServer => {
this._http.post('http://localhost:56877/api/Login/EfectuaLogin',
JSON.stringify({ username, password }),
{
headers: contentHeaders
})
.map((res: any) =>
{
let data = <authLoginModel>res.json();
this.token = data.token;
this.username = data.nome;
sessionStorage.setItem('token', this.token);
sessionStorage.setItem('username', this.username);
return data; // or something else
});
}
});
}
Another approach would be boostrap asynchronously after having loaded the configuration:
var app = platform(BROWSER_PROVIDERS)
.application([BROWSER_APP_PROVIDERS, appProviders]);
service.getConfig().flatMap((url) => {
var configProvider = new Provider('urlServer', { useValue: urlServer});
return app.bootstrap(appComponentType, [ configProvider ]);
}).toPromise();
See this question for the second approach:
angular2 bootstrap with data from ajax call(s)
You can go further by mixing the last approach with a CustomRequestOptions:
import {BaseRequestOptions, RequestOptions, RequestOptionsArgs} from 'angular2/http';
export class CustomRequestOptions extends BaseRequestOptions {
merge(options?:RequestOptionsArgs):RequestOptions {
options.url = 'http://10.7.18.21:8080/api' + options.url;
return super.merge(options);
}
}
See this question:
Angular 2 - global variable for all components