Angular 2 JWT Unit Testing - javascript

My API calls are authenticated with JWT. I am trying to write code for a service method. All requests has this interceptor:
public interceptBefore(request: InterceptedRequest): InterceptedRequest {
// Do whatever with request: get info or edit it
this.slimLoadingBarService.start();
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.data.token) {
request.options.headers.append('Authorization', 'Bearer ' + currentUser.data.token);
}
return request;
}
Service method that I want to test:
getAll(page: number, pageSize: number, company: string): Observable<any> {
return this.http.get(`${this.conf.apiUrl}/jobs`)
.map((response: Response) => response.json());
}
Started the code for it:
import { MockBackend, MockConnection } from '#angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '#angular/http';
import { JobListService } from './job-list.service';
import { inject, TestBed } from '#angular/core/testing/test_bed';
import { JOBLISTMOCK } from '../mocks/job-list.mock';
fdescribe('Service: JobListService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
JobListService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
]
});
});
it('should create a service', inject([JobListService], (service: JobListService) => {
expect(service).toBeTruthy();
}));
describe('getAll', () => {
it('should return jobs', inject([JobListService, MockBackend], (service: JobListService, backend: MockBackend) => {
let response = new ResponseOptions({
body: JSON.stringify(JOBLISTMOCK)
});
const baseResponse = new Response(response);
backend.connections.subscribe(
(c: MockConnection) => c.mockRespond(baseResponse)
);
return service.getAll(1, 10, '18').subscribe(data => {
expect(data).toEqual(JOBLISTMOCK);
});
}));
});
});
Do not know how to test it against the interceptor.
PS: As the tests are now, getting an error:
1) should create a service
JobListService
TypeError: null is not an object (evaluating 'this.platform.injector') in src/test.ts (line 83858)
_createCompilerAndModule#webpack:///~/#angular/core/testing/test_bed.js:254:0 <- src/test.ts:83858:44
2) should return jobs
JobListService getAll
TypeError: null is not an object (evaluating 'this.platform.injector') in src/test.ts (line 83858)
_createCompilerAndModule#webpack:///~/#angular/core/testing/test_bed.js:254:0 <- src/test.ts:83858:44

TypeError: null is not an object (evaluating 'this.platform.injector')
Generally you will get this error if you haven't initialized the test environment correctly. You could solve this problem by doing the following
import {
BrowserDynamicTestingModule, platformBrowserDynamicTesting
} from '#angular/platform-browser-dynamic/testing';
...
beforeAll(() => {
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
});
The thing about this though, is that it should only be called once for the entire test suite execution. So if you have it in every test file, then you need to reset it first in each file
beforeAll(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
});
Better than this though, is to not add it in each test file. If you look at the Angular docs for Webpack integration, in the testing section, you will see a file karma-test-shim.js. In this file is the recommended way to initialize the test environment
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('#angular/core/testing');
var browser = require('#angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting());
You can see at the bottom where we make the same initialization call as above. You should add this file to the karma.conf.js file in the files array in the configuration. This is from the linked documentation above
files: [
{pattern: './config/karma-test-shim.js', watched: false}
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap']
},

Related

Error: Need to call TestBed.initTestEnvironment() first

I'm trying do a test in angular of a service.
This is my part of the code
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService]
});
injector = getTestBed();
service = injector.inject(AddressService);
httpTestingController = injector.inject(HttpTestingController);
// service = TestBed.inject(AddressService);
});
afterEach(() => {
httpTestingController.verify();
})
httpTestingController = TestBed.inject(HttpTestingController);
it('should be created', () => {
expect(service).toBeTruthy();
});
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
it('getAddress() should return data', () => {
service.getAddress().subscribe((res) => {
expect(res).toEqual(dummyAddressListResponse);
});
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
})
});
At the moment of run the test ng test --main src/app/services/address/address.service.spec.ts
I'm seeing this error Error: Need to call TestBed.initTestEnvironment() first
I have searched and don't see any solution, Has it happened to someone?
For jest users - just add the following code in setup-jest.js.
Because jest needs to be initialized.
import { TestBed } from "#angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "#angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
the first thing: --main shouldn't be used, it points to an entrypoint, not to a desired test, and should be src/test.ts.
To run a single test use the next command:
ng test --include "app/services/address/address.service.spec.ts"
The test should be a bit different:
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(async () => {
// let's compile TestBed first.
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService],
}).compileComponents();
// let's use TestBed.injector.
service = TestBed.inject(AddressService);
httpTestingController = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpTestingController.verify();
})
it('should be created', () => {
expect(service).toBeTruthy();
});
it('getAddress() should return data', () => {
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
let actual: any;
service.getAddress().subscribe((res) => actual = res);
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
expect(actual).toEqual(dummyAddressListResponse);
});
});
Is this using ng-packagr (i.e. an angular library)? If so you might want to check that there are no node_modules under the ./project/ folder.
This was throwing me this exact same error. The moment I deleted the node_modules under the project folder it all started to work again.
Source: https://github.com/ngneat/spectator/issues/546
The problem is that it must run everything from test.ts.
So instead of run ng test --main src/app/services/address/address.service.spec.ts command, just change const context = require.context('./', true, /\.spec\.ts$/); to const context = require.context('./', true, /address\.service\.spec\.ts$/); and use ng test command.

Testing Service with Mongoose in NestJS

I am trying to test my LoggingService in NestJS and while I cannot see anything that is wrong with the test the error I am getting is Error: Cannot spy the save property because it is not a function; undefined given instead
The function being tested (trimmed for brevity):
#Injectable()
export class LoggingService {
constructor(
#InjectModel(LOGGING_AUTH_MODEL) private readonly loggingAuthModel: Model<IOpenApiAuthLogDocument>,
#InjectModel(LOGGING_EVENT_MODEL) private readonly loggingEventModel: Model<IOpenApiEventLogDocument>,
) {
}
async authLogging(req: Request, requestId: unknown, apiKey: string, statusCode: number, internalMsg: string) {
const authLog: IOpenApiAuthLog = {
///
}
await new this.loggingAuthModel(authLog).save();
}
}
This is pretty much my first NestJS test and as best I can tell this is the correct way to test it, considering the error is right at the end it seems about right.
describe('LoggingService', () => {
let service: LoggingService;
let mockLoggingAuthModel: IOpenApiAuthLogDocument;
let request;
beforeEach(async () => {
request = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggingService,
{
provide: getModelToken(LOGGING_AUTH_MODEL),
useValue: MockLoggingAuthModel,
},
{
provide: getModelToken(LOGGING_EVENT_MODEL),
useValue: MockLoggingEventModel,
},
],
}).compile();
service = module.get(LoggingService);
mockLoggingAuthModel = module.get(getModelToken(LOGGING_AUTH_MODEL));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('authLogging', async () => {
const reqId = 'mock-request-id';
const mockApiKey = 'mock-api-key';
const mockStatusCode = 200;
const mockInternalMessage = 'mock-message';
await service.authLogging(request, reqId, mockApiKey, mockStatusCode, mockInternalMessage);
const authSpy = jest.spyOn(mockLoggingAuthModel, 'save');
expect(authSpy).toBeCalled();
});
});
The mock Model:
class MockLoggingAuthModel {
constructor() {
}
public async save(): Promise<void> {
}
}
After much more googling I managed to find this testing examples Repo: https://github.com/jmcdo29/testing-nestjs which includes samples on Mongo and also suggest that using the this.model(data) complicates testing and one should rather use `this.model.create(data).
After making that change the tests are working as expected.
The issue comes from the fact that you pass a class to the TestingModule while telling it that it's a value.
Use useClass to create the TestingModule:
beforeEach(async () => {
request = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggingService,
{
provide: getModelToken(LOGGING_AUTH_MODEL),
// Use useClass
useClass: mockLoggingAuthModel,
},
{
provide: getModelToken(LOGGING_EVENT_MODEL),
// Use useClass
useClass: MockLoggingEventModel,
},
],
}).compile();
service = module.get(LoggingService);
mockLoggingAuthModel = module.get(getModelToken(LOGGING_AUTH_MODEL));
});

NestJS accessing private class field before testing method with jest

Assuming there is the following nest service class with the private field myCache and the public method myFunction:
import * as NodeCache from 'node-cache'
class MyService{
private myCache = new NodeCache();
myFunction() {
let data = this.myCache.get('data');
if(data === undefined){
// get data with an http request and store it in this.myCache with the key 'data'
}
return data;
}
}
I want to test the function myFunction for two different cases.
Fist case: If condition is true. Second Case: If condition is false.
Here is the test class with the two missing tests:
import { Test, TestingModule } from '#nestjs/testing';
import { MyService} from './myService';
describe('MyService', () => {
let service: MyService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MyService],
}).compile();
service = module.get<MyService>(MyService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('myFunction', () => {
it('should return chached data', () => {
// first test
}),
it('should return new mocked data', () => {
// second test
})
})
});
Therefore I guess I have to access or mock the myCache private class field.
Because it is private I can't access it in the test class.
My Question is: What's the best and correct way to achieve this?
If you're just looking to mock it, you can always use as any to tell Typescript to not warn you about accessing private values.
jest.spyOn((service as any).myCache, 'get').mockReturnValueOnce(someValue);
However, that's kind of annoying to have to do over and over again and not really the best practice. What I would do instead is move your cache to be an injectable provider so that it could be swapped out at a moments notice and your MyService no longer has a hard dependency on node-cache. Something like this:
// my.module.ts
#Module({
providers: [
MyService,
{
provide: 'CACHE',
useClass: NodeCache
}
]
})
export class MyModule {}
// my.service.ts
#Injectable()
export class MyService {
constructor(#Inject('CACHE') private readonly myCache: NodeCache) {}
...
And now in your test you can swap out the CACHE token for a mock implementation that can also be retrieved in your beforeEach block, meaning no more any.
describe('MyService', () => {
let service: MyService;
let cache: { get; set; }; // you can change the type here
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
MyService,
{
provide: 'CACHE',
useValue: { get: jest.fn(), set: jest.fn() }
}
]
}).compile();
service = modRef.get(MyService);
cache = modRef.get<{ get; set; }>('CACHE');
});
});
And now you can call jest.spyOn(cache, 'get') without the use of as any.

Observable "source is deprecated" error on npm lint

I get "source is deprecated: This is an internal implementation detail, do not use." when I run the command npm lint on my code below:
set stream(source: Observable<any>) {
this.source = source;
}
If I take it out, it satisfies the lint, but it breaks my unit tests. Why is this?
If you are testing effects, you need to update the approach. I have changed using the provideMockActions, the action would be an let actions$: Observable;
fdescribe('PizzaEffects', () => {
let actions$: Observable;;
let service: Service;
let effects: PizzaEffects;
const data = givenPizzaData();
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ApolloTestingModule],
providers: [
Service,
PizzaEffects,
Apollo,
// { provide: Actions, useFactory: getActions }, remove
provideMockActions(() => actions$),
]
});
actions$ = TestBed.get(Actions);
service = TestBed.get(Service);
effects = TestBed.get(PizzaEffects);
spyOn(service, 'loadData').and.returnValue(of(data));
});
describe('loadPizza', () => {
it('should return a collection from LoadPizzaSuccess', () => {
const action = new TriggerAction();
const completion = new LoadPizzaSuccess(data);
actions$ = hot('-a', { a: action });
const expected = cold('-b', { b: completion });
expect(effects.getPizzaEffect$).toBeObservable(expected);
});
});
});

Angular 2 mock Http get() to return local json file

What is the easiest way to mock the response returned by Http get() in Angular 2?
I have local data.json file in my working directory, and I want get() to return response containing that data as a payload, simulating the rest api.
Documents for configuring the Backend object for Http seemed somewhat obscure and overcomplicated for such a simple task.
You need to override the XhrBackend provider with the MockBackend one. You need then to create another injector to be able to execute a true HTTP request.
Here is a sample:
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
SomeHttpService
];
});
it('Should something', inject([XHRBackend, SomeHttpService], (mockBackend, httpService) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
var injector = ReflectiveInjector.resolveAndCreate([
HTTP_PROVIDERS
]);
var http = injector.get(Http);
http.get('data.json').map(res => res.json()).subscribe(data) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: data
})));
});
});
}));
By the way, you need to mock the XHRBackend and provide mocked data in a class with the createDb method. createDb method returns the mocked JSON object. To load that data provide correct URL to http.get, for example, if JSON object is contained in a variable mockedObject, then the URL should be "app\mockedObject".
You can read more details here: https://angular.io/docs/ts/latest/guide/server-communication.html.
You can use the HttpTestingController available via the core TestBed as to me it feels more intuitive (each to their own, of course). Untested snippet:
import { TestBed, async } from '#angular/core/testing';
import { HttpTestingController } from '#angular/common/http/testing';
import { MyApiService } from './my-api.service';
export function main() {
describe('Test set', () => {
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [MyApiService]
});
httpMock = TestBed.get(HttpTestingController);
});
it('should call get', async(() => {
const data: any = {mydata: 'test'};
let actualResponse: any = null;
MyApiService.get().subscribe((response: any) => {
actualResponse = response;
});
httpMock.expectOne('localhost:5555/my-path').flush(data);
expect(actualResponse).toEqual(data);
}));
});
}

Categories

Resources