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.
Related
I am trying to write a unit test (with jest) that asserts that data is being pushed into job_execs. The two issues I'm running into are mocking the api endpoint and if it's even possible to mock this.$route.params.tool in a unit test.
So the main question is, can I write a test with the component below? I know that it should be possible to mock the api endpoint (still haven't figured out how to), but my concern is that I have too many external dependencies in the component for this to be unit-testable. Do I need to rewrite my component to support unit tests?
Jobs.vue
<script>
export default {
name: "Jobs",
data() {
return {
job_execs: []
}
},
created() {
this.JobExecEndpoint = process.env.VUE_APP_UATU_URL + '/api/v1/job_execution/?tool='+this.$route.params.tool+'&job='+this.$route.params.job+'&id='+this.$route.params.id
fetch(this.JobExecEndpoint)
.then(response => response.json())
.then(body => {
this.job_execs.push({
'code_checkouts': body[0].code_checkouts,
})
})
.catch( err => {
console.log('Error Fetching:', this.JobExecEndpoint, err);
return { 'failure': this.JobExecEndpoint, 'reason': err };
})
},
};
</script>
unit test
import { shallowMount } from "#vue/test-utils";
import fetchMock from 'fetch-mock'
import flushPromises from 'flush-promises'
import Jobs from "../../src/components/execution_details/Jobs.vue";
const job_execs = [
{
'code_checkouts': [{'name': 'test', 'git_hash': 'test', 'git_repo': 'test'}, {'name': 'test', 'git_hash': 'test', 'git_repo': 'test'}]}]
const $route = {
params = {
tool: 'jenkins',
job: 'jobname',
id: '1',
}
}
describe('Jobs.vue', () => {
beforeEach(() => {
fetchMock.get(process.env.VUE_APP_UATU_URL + '/api/v2/job_execution/?product=eBay%20Mobile&tool='+$route.params.tool+'&job='+$route.params.job+'&id='+$route.params.id, job_execs)
})
it('Construct a JSON object of Git Refs from job_execution API', async () => {
const wrapper = shallowMount(GitRefs)
await flushPromises()
expect(wrapper.vm.job_execs).toEqual(job_execs)
})
afterEach(() => {
fetchMock.restore()
})
})
You need to import all the dependencies used in the component and also need to import whatever global values or properties used router, axios etc
I have a node module which exports a few classes, one of which is Client, which I use to create a client (having a few APIs as methods).
I'm trying to test my module which uses this node module as a dependency using Jest. However, I've been unable to successfully mock the one method (say search()) in the Client class.
Here is my spec for myModule:
//index.spec.ts
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
jest.mock('node-module-name');
const mockedClient = <jest.Mock<externalModule.Client>>externalModule.Client;
const myClient = createClient({/*params*/}); //returns instance of Client class present in node module by executing Client() constructor
myClient.searchByName('abc'); //calls search API - I need to track calls to this API
expect(mockedClient).toHaveBeenCalled();
expect(mockedClient.prototype.search).toHaveBeenCalledWith('abc');
});
});
This, however, doesn't create a mock at all and triggers a nock error since the search API tries to connect to the url (given through params).
I've also tried mocking the Client class like the following. While successfully creating a mock for the Client class and also the search API (verified that search() is also mocked through console logs), it gives me an error while I try to check if search() has been called.
externalModule.Client = jest.fn(() => { return { search: jest.fn(() => Promise.resolve('some response')) } });
//creates the mock successfully, but not sure how to track calls to 'search' property
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled(); //Successful
expect(externalModule.Client.prototype.search).toHaveBeenCalled(); //returns error saying "jest.fn() value must be a mock function or spy, Received: undefined"
I'm not sure what I'm doing wrong. Thank you in advance.
Mocking whole module
Try moving jest.mock to the top of file
//index.spec.ts
const search = jest.fn();
jest.mock('node-module-name', () => ({
Client: jest.fn(() => ({ search }))
}));
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
const myClient = createClient({/*params*/});
myClient.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalledWith('abc');
externalModule.Client.mockClear();
search.mockClear();
});
});
Mocking only Client
Create search constant and track it.
const search = jest.fn();
externalModule.Client = jest.fn(() => ({ search }));
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalled();
Here is how I mocked it. I had to change naming and removing some code to avoid exposing original source.
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
Full code.
// foo-client.ts
export class FooClient {
constructor(private config: any)
post() {}
}
// foo-service.ts
import { FooClient } from './foo-client'
export class FooLabelService {
private client: FooClient
constructor() {
this.client = new FooClient()
}
createPost() {
return this.client.post()
}
}
// foo-service-test.ts
import { FooService } from '../foo-service'
const mockPost = jest.fn()
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
describe('FooService', () => {
let fooService: FooService
beforeEach(() => {
jest.resetAllMocks()
fooService = new FooService()
})
it('something should happened', () => {
mockPost.mockResolvedValue()
fooService.createPost()
})
})
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);
});
});
});
Current behavior
Nest can't resolve dependencies of the ElasticsearchService (?). Please make sure that the argument at index [0] is available in the ElasticsearchModule context.
Expected behavior
Create ElasticSearchService in test module
Minimal reproduction of the problem with instructions
import { Test, TestingModule } from '#nestjs/testing';
import { RepliesController } from './replies.controller';
import { ElasticsearchService, ElasticsearchModule } from '#nestjs/elasticsearch';
import { Client } from 'elasticsearch';
describe('Replies Controller', () => {
let module: TestingModule;
let elasticsearch: ElasticsearchService;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController],
components: [ElasticsearchService, {provide: Client, useValue: {}}],
}).compile();
elasticsearch = module.get<ElasticsearchService>(ElasticsearchService);
});
it('should be defined', () => {
const controller: RepliesController = module.get<RepliesController>(RepliesController);
expect(controller).toBeDefined();
});
});
Environment
[Nest Information]
elasticsearch version : 0.1.2
common version : 5.4.0
core version : 5.4.0
You need to override the providers using methods exposed by the test module instead of doing it in the components array:
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController]
})
.overrideProvider(ElasticsearchService)
.useValue(/* your mock or whatever */)
.compile();
There are examples of this in the NestJs docs in the testing section.
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']
},