angular2 unit testing with dependent services - unit-testing

I'm new to angular2 and trying to write tests for services. I have a userservice in my application which is a dependent service on multiple other services and params. The user service is making an rest-api call in the backend to pull user information.
I'm having a hard time to understand what is the correct way to inject the dependent services to this user service, so it can make a http call and get response.
user-service.ts
export class UserService extends HttpService {
private _authToken: string;
private _persistent: boolean;
constructor(http: Http, private sessionService: SessionService, private router: Router) {
super(http);
this._authToken = localStorage.getItem(AUTH_TOKEN_KEY);
}
get authToken(): string {
return this._authToken;
}
session-service.ts
export class SessionService extends HttpService {
constructor(http: Http) {
super(http);
this.rootUrl = Constants.API_ROOT;
}
addSession(authToken: string, persistent:boolean=false): Observable<any> {
return this.post(SESSIONS_ROOT, {persistent: persistent}, TokenService.AuthHeader(authToken));
}
user-service.spec.ts
describe(' ####### User Service Test Cases ####### ', () => beforeEach(() => {
TestBed.configureTestingModule({
providers: [
// {
// provide: Router,
// useClass: class { navigate = jasmine.createSpy("navigate")},
// },
BaseRequestOptions,
MockBackend,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions],
},
SessionService,
UserService
],
imports: [RouterTestingModule]
}); }); it('test method', inject([UserService], (userService: UserService) => {
expect(userService.isEmailAvailable('a#a.com')).toBe('true');
}));
});
But, I'm getting error when executing.
####### User Service Test Cases #######
×should return response when email is already subsribed
Expected AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), source: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), operator: TakeOperator({ total: 1 }) }), source: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), source: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), operator: TakeOperator({ total: 1 }) }), operator: MapOperator({ project: Function, thisArg: undefined }) }), source: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), source: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), operator: TakeOperator({ total: 1 }) }), source: AnonymousSubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, destination: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), source: ReplaySubject({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, scheduler: undefined, _events: [ ], _bufferSize: 1, _windowTime: Infinity }), operator: TakeOperator({ total: 1 }) }), operator: MapOperator({ project: Function, thisArg: undefined }) }), operator: CatchOperator({ selector: Function, caught: }) }) to be 'true'.
at webpack:///src/app/test/user.spec.ts:75:69 <- config/karma-test-shim.js:87887:71 [ProxyZone]
I'm not sure what to understand from the error. Any help on how to proceed or inject dependent services correctly and create there instances will be help.
Thank you so much. Appreciate the help.

Related

Occasional Http requests timeout on AWS Lambda

I've got an AWS lambda (as node.js) that is triggered by cron every 3 minutes. it purpose is to monitor some of our API's services, so it is sends http requests. Issue is, in 20% of the cases the http request times out. when I run it locally, or manually (by using the Test feature on AWS console) everything works ok. I tried to increase the timeout option but with no success.
Error Im getting is:
timeout of 5000ms exceeded at createError (/var/task/node_modules/axios/lib/core/createError.js
Error Details:
2021-08-30T09:06:08.029Z 841332b5-d8c1-49dc-8caa-f57215ed5440 ERROR PortalApiSql_Error Error: timeout of 5000ms exceeded
at createError (/var/task/node_modules/axios/lib/core/createError.js:16:15)
at RedirectableRequest.handleRequestTimeout (/var/task/node_modules/axios/lib/adapters/http.js:280:16)
at RedirectableRequest.emit (events.js:400:28)
at Timeout._onTimeout (/var/task/node_modules/follow-redirects/index.js:166:12)
at listOnTimeout (internal/timers.js:557:17)
at processTimers (internal/timers.js:500:7) {
config: {
url: '/health/sql',
method: 'get',
headers: {
Accept: 'application/json, text/plain, /',
ApiToken: '---',
'User-Agent': 'axios/0.21.1'
},
baseURL: 'https://apidev.myportal.com/v1',
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 5000,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
data: undefined
},
code: 'ECONNABORTED',
request: <ref 1> Writable {
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
errored: null,
closed: false
},
_events: [Object: null prototype] {
response: [Array],
error: [Array],
socket: [Function: destroyOnTimeout]
},
_eventsCount: 3,
_maxListeners: undefined,
_options: {
maxRedirects: 21,
maxBodyLength: 10485760,
protocol: 'https:',
path: '/v1/health/sql',
method: 'GET',
headers: [Object],
agent: undefined,
agents: [Object],
auth: undefined,
hostname: 'apidev.myportal.com',
port: null,
nativeProtocols: [Object],
pathname: '/v1/health/sql'
},
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: ClientRequest {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [TLSSocket],
_header: 'GET /v1/health/sql HTTP/1.1\r\n' +
'Accept: application/json, text/plain, /\r\n' +
'ApiToken: ----\r\n' +
'User-Agent: axios/0.21.1\r\n' +
'Host: apidev.myportal.com\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: noopPendingOutput],
agent: [Agent],
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/v1/health/sql',
_ended: false,
res: null,
aborted: true,
timeoutCb: null,
upgradeOrConnect: false,
parser: [HTTPParser],
maxHeadersCount: null,
reusedSocket: false,
host: 'apidev.myportal.com',
protocol: 'https:',
_redirectable: [Circular *1],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype]
},
_currentUrl: 'https://apidev.myportal.com/v1/health/sql',
_timeout: Timeout {
_idleTimeout: 5000,
_idlePrev: null,
_idleNext: null,
_idleStart: 149306,
_onTimeout: [Function (anonymous)],
_timerArgs: undefined,
_repeat: null,
_destroyed: true,
[Symbol(refed)]: true,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 63,
[Symbol(triggerId)]: 59
},
[Symbol(kCapture)]: false
},
response: undefined,
isAxiosError: true,
toJSON: [Function: toJSON]
}
I've simplified the code (simplified code also gets the timeout error),
exports.handler = async (event, context) => {
const axiosInstance = axios.create({
baseURL: PortalApiUrl,
timeout: AxiosTimeout,
headers: { ApiToken: PortalApiToken },
});
try {
console.log("before req ");
console.log(WATCHERS_CONFIGURATION.PortalApiSql.Path);
const response = await axiosInstance.get(WATCHERS_CONFIGURATION.PortalApiSql.Path);
console.log(`recieved ${response}`);
return {
statusCode: 200,
};
} catch (error) {
console.error("Error: ", error);
return {
statusCode: 500,
};
}
};

Jest - No tests found

I have specific situation. I have something like common module that define common behaviour of all integrations and all integrations is using that as a library. In that common module, I have configuration of jest and set of tests.
Everything works OK in case that common module is linked (npm link) to integration. But when this this common module is installed directly from npm, jest is not able to find any tests. I suppose, there are some files ignored, but I was not abe to find any configuration that would solve this situation.
Final configuration from debug mode looks like this:
{
"configs": [
{
"automock": false,
"cache": true,
"cacheDirectory": "/private/var/folders/mv/v_zhxfq113qf8d6vsf4ldwq80000gn/T/jest_dx",
"clearMocks": false,
"coveragePathIgnorePatterns": [
"/node_modules/"
],
"cwd": "<path>/Workspace/integration/my_integration",
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
"extraGlobals": [],
"forceCoverageMatch": [],
"globals": {},
"haste": {
"computeSha1": false,
"throwOnModuleCollision": false
},
"moduleDirectories": [
"node_modules"
],
"moduleFileExtensions": [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
"moduleNameMapper": [],
"modulePathIgnorePatterns": [],
"name": "24eedccdafdba030f3d9209ab1064c8e",
"prettierPath": "prettier",
"resetMocks": false,
"resetModules": false,
"restoreMocks": false,
"rootDir": "<path>/Workspace/integration/my_integration/node_modules/common_module/tests/api",
"roots": [
"<path>/Workspace/integration/my_integration/node_modules/common_module/tests/api"
],
"runner": "jest-runner",
"setupFiles": [],
"setupFilesAfterEnv": [],
"skipFilter": false,
"slowTestThreshold": 5,
"snapshotSerializers": [],
"testEnvironment": "<path>/Workspace/integration/my_integration/node_modules/jest-environment-node/build/index.js",
"testEnvironmentOptions": {},
"testLocationInResults": false,
"testMatch": [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
"testPathIgnorePatterns": [
"/node_modules/"
],
"testRegex": [],
"testRunner": "<path>/Workspace/integration/my_integration/node_modules/jest-jasmine2/build/index.js",
"testURL": "http://localhost",
"timers": "real",
"transform": [],
"transformIgnorePatterns": [
"/node_modules/",
"\\.pnp\\.[^\\/]+$"
],
"watchPathIgnorePatterns": []
}
],
"globalConfig": {
"bail": 0,
"changedFilesWithAncestor": false,
"collectCoverage": false,
"collectCoverageFrom": [],
"coverageDirectory": "<path>/Workspace/integration/my_integration/node_modules/common_module/coverage",
"coverageProvider": "babel",
"coverageReporters": [
"json",
"text",
"lcov",
"clover"
],
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
"expand": false,
"findRelatedTests": false,
"forceExit": false,
"json": false,
"lastCommit": false,
"listTests": false,
"logHeapUsage": false,
"maxConcurrency": 5,
"maxWorkers": 7,
"noStackTrace": false,
"nonFlagArgs": [],
"notify": false,
"notifyMode": "failure-change",
"onlyChanged": false,
"onlyFailures": false,
"passWithNoTests": false,
"projects": [],
"rootDir": "<path>/Workspace/integration/my_integration/node_modules/common_module/tests/api",
"runTestsByPath": false,
"skipFilter": false,
"testFailureExitCode": 1,
"testPathPattern": "",
"testSequencer": "<path>/Workspace/integration/my_integration/node_modules/#jest/test-sequencer/build/index.js",
"updateSnapshot": "new",
"useStderr": false,
"verbose": true,
"watch": false,
"watchAll": false,
"watchman": true
},
"version": "26.4.0"
}
All tests are defined on path <path>/Workspace/integration/my_integration/node_modules/common_module/tests/api named as e.g. something.test.js.
Console will show allways:
No tests found, exiting with code 1
Run with `--passWithNoTests` to exit with code 0
No files found in <path>/Workspace/integration/my_integration/node_modules/common_module/tests/api.
Make sure Jest's configuration does not exclude this directory.
To set up Jest, make sure a package.json file exists.
Jest Documentation: facebook.github.io/jest/docs/configuration.html
Pattern: - 0 matches
npm ERR! Test failed. See above for more details.

React-native + ts-jest + enzyme hanging after test complete

I'm creating unit testing for a React native application, and what happens is, after the test initiate , it hangs , without any response from the terminal . I've tried to use --forceExit , --detectOpenHandles to log any problem. but the terminal continues as follow:
I've Intellij IDE , and running the same test on it I've this result
Note that where is written Running Tests... it's shows a loading icon, the test continues running on Intellij even after the test were completed.
here's the code of my testing :
/**
* #jest-environment jsdom
*/
import React from 'react';
import { mount, ReactWrapper, shallow } from 'enzyme';
import AnotherComponent from 'components/AnotherComponent';
// #ts-ignore
import { mocking } from './helpers/mockedTheme';
import TestComponent from 'components/TestComponent';
export const shallowWithTheme = (children: any) => (
// #ts-ignore
shallow(children, { mocking })
);
describe('Test Components', () => {
describe('Test Component', ()=>{
it('Test shallow with theme', () => {
const element = shallowWithTheme(<TestComponent componentProp={"test"}/>);
//#ts-ignore
expect(element).toExist();
});
});
describe('Test AnotherComponent', () =>{
it('AnotherComponent with button', () => {
const wrapper = shallowWithTheme(
<AnotherComponent icon="qr-code"
title='component title'
description='component description'
buttonText='go'
/>);
// #ts-ignore
expect(wrapper).toExist();
});
it('AnotherComponent without button', () => {
const wrapper = shallow(
<AnotherComponent icon="qr-code"
title='component title'
description='component description'
/>);
//#ts-ignore
expect(wrapper).toExist();
});
});
});
The components AnotherComponent and TestComponent are nested with another styled.components eg,:
\\ TestComponent
import React from 'react';
import { MainContentView } from 'components/TestComponent/TestComponent.styles';
import { throwJSError } from 'reduxRoot/util/error';
import { withTheme } from 'styled-components/native';
import { Text } from 'react-native';
class TestComponent extends React.Component {
render() {
try {
return (
<MainContentView>
<Text>Text</Text>
</MainContentView>
);
} catch (err) {
throwJSError(err, 'TestComponent/render');
return null;
}
}
}
export default withTheme(TestComponent);
The style MainContentView is a styled.component eg.:
export const MainContentView = styled.View` flex: 1; backgroundColor: #FFFFFF;`;
here my jest ---debug
{
"configs": [
{
"automock": false,
"browser": false,
"cache": true,
"cacheDirectory": "/private/var/folders/rk/rnc3852s3y56rx7bypry2qz92b2nhd/T/jest_j25hjh",
"clearMocks": false,
"coveragePathIgnorePatterns": [
"/node_modules/"
],
"cwd": "<root-to-my-project>",
"dependencyExtractor": null,
"detectLeaks": false,
"detectOpenHandles": true,
"errorOnDeprecated": false,
"filter": null,
"forceCoverageMatch": [],
"globalSetup": null,
"globalTeardown": null,
"globals": {
"ts-jest": {
"babelConfig": true,
"isolatedModules": true
}
},
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
],
"hasteImplModulePath": "<root-to-my-project>/node_modules/react-native/jest/hasteImpl.js",
"providesModuleNodeModules": [
"react-native"
]
},
"moduleDirectories": [
"node_modules"
],
"moduleFileExtensions": [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
"moduleNameMapper": [
[
"reduxRoot(.*)$",
"<root-to-my-project>/redux$1"
],
[
"screens(.*)$",
"<root-to-my-project>/src/screens$1"
],
[
"^React$",
"<root-to-my-project>/node_modules/react/index.js"
]
],
"modulePathIgnorePatterns": [
"<root-to-my-project>/node_modules/react-native/Libraries/react-native/"
],
"name": "a1c9dcb3024a2c47c9517df7e59341e3",
"prettierPath": "prettier",
"resetMocks": false,
"resetModules": false,
"resolver": null,
"restoreMocks": false,
"rootDir": "<root-to-my-project>",
"roots": [
"<root-to-my-project>"
],
"runner": "jest-runner",
"setupFiles": [
"<root-to-my-project>/node_modules/react-native/jest/setup.js",
"<root-to-my-project>/native/node_modules/react-native-gesture-handler/jestSetup.js"
],
"setupFilesAfterEnv": [
"<root-to-my-project>/jest.setup.js"
],
"skipFilter": false,
"snapshotSerializers": [],
"testEnvironment": "<root-to-my-project>/node_modules/jest-environment-node/build/index.js",
"testEnvironmentOptions": {},
"testLocationInResults": false,
"testMatch": [
"**/__tests__/*.(ts|tsx|js)"
],
"testPathIgnorePatterns": [
"/node_modules/"
],
"testRegex": [],
"testRunner": "<root-to-my-project>/node_modules/jest-jasmine2/build/index.js",
"testURL": "http://localhost",
"timers": "real",
"transform": [
[
"^.+\\.tsx?$",
"<root-to-my-project>/node_modules/ts-jest/dist/index.js"
],
[
"\\.js$",
"<root-to-my-project>/node_modules/react-native/jest/preprocessor.js"
],
[
"^.+\\.(js|ts|tsx)$",
"<root-to-my-project>/node_modules/babel-jest/build/index.js"
],
[
"^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$",
"<root-to-my-project>/node_modules/react-native/jest/assetFileTransformer.js"
]
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|#react-native-community)"
],
"watchPathIgnorePatterns": []
}
],
"globalConfig": {
"bail": 0,
"changedFilesWithAncestor": false,
"collectCoverage": false,
"collectCoverageFrom": null,
"coverageDirectory": "<root-to-my-project>/coverage",
"coverageReporters": [
"json",
"text",
"lcov",
"clover"
],
"coverageThreshold": null,
"detectLeaks": false,
"detectOpenHandles": true,
"errorOnDeprecated": false,
"expand": false,
"filter": null,
"globalSetup": null,
"globalTeardown": null,
"json": false,
"listTests": false,
"maxConcurrency": 5,
"maxWorkers": 3,
"noStackTrace": false,
"nonFlagArgs": [],
"notify": false,
"notifyMode": "failure-change",
"passWithNoTests": false,
"projects": null,
"rootDir": "<root-to-my-project>",
"runTestsByPath": false,
"skipFilter": false,
"testFailureExitCode": 1,
"testPathPattern": "",
"testResultsProcessor": null,
"testSequencer": "<root-to-my-project>/node_modules/#jest/test-sequencer/build/index.js",
"updateSnapshot": "new",
"useStderr": false,
"verbose": true,
"watch": false,
"watchman": true
},
"version": "24.9.0"
}
And last but not least , my jest.setup.js to mock all I need to run my enviroment:
import { NativeModules } from 'react-native';
import 'jest-styled-components';
import 'react-native';
import 'jest-enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { configure } from 'enzyme';
/**
* Set up Enzyme to mount to DOM, simulate events,
* and inspect the DOM in tests.
*/
configure({ adapter: new Adapter() });
/**
* Set up DOM in node.js environment for Enzyme to mount to
*/
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
const { window } = jsdom;
function copyProps(src, target) {
Object.defineProperties(target, {
...Object.getOwnPropertyDescriptors(src),
...Object.getOwnPropertyDescriptors(target),
});
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
jest.mock('react-navigation/src/routers/KeyGenerator', () => ({
generateKey: jest.fn(() => 123),
}));
jest.mock('./node_modules/react-native-reanimated/src/ReanimatedEventEmitter');
jest.mock('./node_modules/react-native-reanimated/src/ReanimatedModule');
jest.mock('react-native-gesture-handler', () =>({
State: jest.fn(),
TapGestureHandler: jest.fn()
}));
NativeModules.RNCNetInfo = {
getCurrentState: jest.fn(() => Promise.resolve()),
addListener: jest.fn(),
removeListeners: jest.fn()
};
jest.mock('react-native-device-info', () => {
return {
getVersion: jest.fn(() => Promise.resolve('1.0')),
getApplicationName: jest.fn(() => Promise.resolve('My App')),
getModel: jest.fn(() => Promise.resolve('iPhone 11')),
hasNotch: jest.fn(),
};
});
jest.mock('services/theme/scaling', () => ({
scale: jest.fn()
})
);
jest.mock('services/theme/styles', () => ({
themeVal: jest.fn()
})
);
NativeModules.StatusBarManager = {
HEIGHT : 20,
getHeight: jest.fn()
};
NativeModules.ParentBridge ={
partnerId: jest.fn()
};
NativeModules.CTNConfig = { buildEnvironment: 'Development'};
So. Has someone knows how to configure properly my code to be able to see my unit testing finishing and not hanging anymore?
ps.: I've note I've recieving a warning message, don't know this is related on the reasons the test is hanging:
Warning: Async Storage has been extracted from react-native core and will be removed in a future release. It can now be installed and imported from '#react-native-community/async-storage' instead of 'react-native'. See https://github.com/react-native-community/react-native-async-storage```

3rd Party library testing angular 2 webpack

I am working on a project using angular 2 and a 3rd party library for making charts (AmCharts). I figured out how to use it along angular 2, but I'm getting a error when I try to make a unit test for the chart component:
Error: Error in ./ChartsComponent class ChartsComponent - inline template:1:2 caused by: AmCharts is not defined
ReferenceError: AmCharts is not defined
This project has been created using angular-cli and we've recently upgraded angular to version 2.2.1.
Here is angular-cli.json and karma.conf.js:
angular-cli.json
{
"project": {
"version": "1.0.0-beta.21",
"name": "cli-crud-webpack"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"test": "test.ts",
"tsconfig": "tsconfig.json",
"prefix": "gov",
"mobile": false,
"styles": [
"styles.scss",
"../public/assets/css/agate.css",
"../public/assets/css/bootstrap.min.css"
],
"scripts": [
"../public/assets/js/highlight.pack.js",
"../node_modules/amcharts3/amcharts/amcharts.js",
"../node_modules/amcharts3/amcharts/xy.js",
"../node_modules/amcharts3/amcharts/gauge.js",
"../node_modules/amcharts3/amcharts/serial.js",
"../node_modules/amcharts3/amcharts/pie.js",
"../node_modules/amcharts3/amcharts/themes/light.js",
"../node_modules/amcharts3/amcharts/themes/dark.js",
"../node_modules/amcharts3/amcharts/themes/black.js",
"../node_modules/amcharts3/amcharts/plugins/responsive/responsive.min.js"
],
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"addons": [],
"packages": [],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": true,
"module": false,
"pipe": true,
"service": true
}
}
}
karma.conf.js
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'angular-cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-remap-istanbul'),
require('angular-cli/plugins/karma')
],
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['angular-cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
angularCli: {
config: './angular-cli.json',
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'karma-remap-istanbul']
: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
I just found out I have to declare amcharts in karma configuration file, like this:
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'angular-cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-remap-istanbul'),
require('angular-cli/plugins/karma')
],
files: [
{ pattern: './src/test.ts', watched: false },
"./public/assets/js/highlight.pack.js",
"./node_modules/amcharts3/amcharts/amcharts.js",
"./node_modules/amcharts3/amcharts/xy.js",
"./node_modules/amcharts3/amcharts/gauge.js",
"./node_modules/amcharts3/amcharts/serial.js",
"./node_modules/amcharts3/amcharts/pie.js",
"./node_modules/amcharts3/amcharts/themes/light.js",
"./node_modules/amcharts3/amcharts/themes/dark.js",
"./node_modules/amcharts3/amcharts/themes/black.js",
"./node_modules/amcharts3/amcharts/plugins/responsive/responsive.min.js"
],
preprocessors: {
'./src/test.ts': ['angular-cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
angularCli: {
config: './angular-cli.json',
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'karma-remap-istanbul']
: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

Setting up unit test code coverage on Angular 2.1.0 Webpack

I am Following this tutorial on setting up a Webpack Angular 2 project.
I can run unit tests just fine with the setup, but I have tried adding code coverage to the project using karma-coverage and remap-istanbul, but it seems that karma-coverage is not outputting anything in the coverage-final.json.
What do I need to add to the karma config to get the test config to work?
Here is my current config:
var webpackConfig = require('./webpack.test');
module.exports = function (config) {
var _config = {
basePath: '',
frameworks: ['jasmine'],
files: [
{pattern: './config/karma-test-shim.js', watched: false}
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['PhantomJS'],
singleRun: true
};
config.set(_config);
};
You have two options, the easiest way is to use angular-cli. The hardest way is based on that tutorial make the changes needed for code coverage, which are a lot. One of the main things that you will be forced is to change to Webpack 2, I wasn't able to make awesome-typescript-loader work with karma using Webpack 1. The code coverage was always empty. I got some inspiration from angular-cli and from angular2-webpack-starter here are the changes:
karma.conf.js: add this:
remapIstanbulReporter: {
reports: {
html: 'coverage',
lcovonly: './coverage/coverage.lcov'
}
},
And change this:
reporters: ['progress'],
to this:
reporters: ['progress', 'karma-remap-istanbul'],
There are a lot of changes to the webpack configs so I'm just going to paste the entire config files, it's easier:
webpack.common.js:
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
module.exports = {
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader'],
exclude: [/\.(spec|e2e)\.ts$/]
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract({
fallbackLoader: 'style-loader',
loader: 'css-loader'
})
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// Optimizing ensures loading order in index.html
name: ['polyfills', 'vendor', 'app'].reverse()
}),
new webpack.optimize.CommonsChunkPlugin({
minChunks: Infinity,
name: 'inline',
filename: 'inline.js',
sourceMapFilename: 'inline.map'
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
};
webpack.dev.js
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
sourceMapFilename: '[name].map',
library: 'ac_[name]',
libraryTarget: 'var'
},
plugins: [
new webpack.LoaderOptionsPlugin({
options: {
tslint: {
emitErrors: false,
failOnHint: false,
resourcePath: 'src'
},
}
}),
new ExtractTextPlugin('[name].css')
],
devServer: {
historyApiFallback: true,
stats: 'minimal',
watchOptions: {
aggregateTimeout: 300,
poll: 1000
},
outputPath: helpers.root('dist')
},
node: {
global: true,
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
});
webpack.prod.js:
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var WebpackMd5Hash = require('webpack-md5-hash');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
path: helpers.root('dist'),
filename: '[name].[chunkhash].js',
sourceMapFilename: '[name].[chunkhash].bundle.map',
chunkFilename: '[id].[chunkhash].chunk.js'
},
plugins: [
new WebpackMd5Hash(),
new webpack.NoErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: { screw_ie8: true },
compress: { screw_ie8: true }
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
options: {
tslint: {
emitErrors: true,
failOnHint: true,
resourcePath: helpers.root('src')
},
htmlLoader: {
minimize: true,
removeAttributeQuotes: false,
caseSensitive: true,
customAttrSurround: [
[/#/, /(?:)/],
[/\*/, /(?:)/],
[/\[?\(?/, /(?:)/]
],
customAttrAssign: [/\)?\]?=/]
}
}
}),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
helpers.root('src')
)
],
node: {
fs: 'empty',
global: true,
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
});
webpack.test.js:
var helpers = require('./helpers');
var path = require('path');
var atl = require('awesome-typescript-loader');
var webpack = require('webpack');
module.exports = {
devtool: 'inline-source-map',
context: path.resolve(__dirname, './'),
resolve: {
extensions: ['.ts', '.js'],
plugins: [
new atl.TsConfigPathsPlugin({
tsconfig: helpers.root('tsconfig.json')
})
]
},
entry: {
test: helpers.root('config/karma-test-shim')
},
output: {
path: './dist.test',
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
exclude: [
helpers.root('node_modules')
]
},
{
test: /\.js$/,
enforce: 'pre',
loader: 'source-map-loader',
exclude: [
helpers.root('node_modules/rxjs'),
helpers.root('node_modules/#angular')
]
},
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
query: {
tsconfig: helpers.root('tsconfig.json'),
module: 'commonjs',
target: 'es5',
useForkChecker: true
}
},
{
loader: 'angular2-template-loader'
}
],
exclude: [/\.e2e\.ts$/]
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: 'null'
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
},
{
test: /\.(js|ts)$/, loader: 'sourcemap-istanbul-instrumenter-loader',
enforce: 'post',
exclude: [
/\.(e2e|spec)\.ts$/,
/node_modules/
],
query: { 'force-sourcemap': true }
},
]
},
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: null, // if no value is provided the sourcemap is inlined
test: /\.(ts|js)($|\?)/i // process .js and .ts files only
}),
new webpack.LoaderOptionsPlugin({
options: {
tslint: {
emitErrors: false,
failOnHint: false,
resourcePath: `./src`
}
}
}),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
helpers.root('src')
)
],
node: {
fs: 'empty',
global: true,
process: false,
crypto: 'empty',
module: false,
clearImmediate: false,
setImmediate: false
}
}
package.json:
You will need to install new packages and update your start script to this:
"start": "webpack-dev-server --config config/webpack.dev.js --profile --watch --content-base src/",
And install these packages:
npm i -D extract-text-webpack-plugin#2.0.0-beta.4 karma-remap-istanbul source-map-loader sourcemap-istanbul-instrumenter-loader tslint tslint-loader webpack#2.1.0-beta.25 webpack-dev-server#2.1.0-beta.3 webpack-md5-hash
Last but not least we just need to do some changes on the tsconfig.json and since we are now using tslint we add the a tslint.json file.
tsconfig.json:
{
"compilerOptions": {
"buildOnSave": false,
"compileOnSave": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist/out-tsc",
"noImplicitAny": true,
"removeComments": false,
"sourceMap": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5"
}
}
tslint.json:
{
"rules": {
"member-access": false,
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-inferrable-types": false,
"no-internal-module": true,
"no-var-requires": false,
"typedef": false,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "space",
"index-signature": "space",
"parameter": "space",
"property-declaration": "space",
"variable-declaration": "space"
}
],
"ban": false,
"curly": false,
"forin": true,
"label-position": true,
"label-undefined": true,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-null-keyword": false,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-unreachable": true,
"no-unused-expression": true,
"no-unused-variable": false,
"no-use-before-declare": true,
"no-var-keyword": true,
"radix": true,
"switch-default": true,
"triple-equals": [
true,
"allow-null-check"
],
"use-strict": [
true,
"check-module"
],
"eofline": true,
"indent": [
true,
"spaces"
],
"max-line-length": [
true,
100
],
"no-require-imports": false,
"no-trailing-whitespace": true,
"object-literal-sort-keys": false,
"trailing-comma": [
true,
{
"multiline": false,
"singleline": "never"
}
],
"align": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"interface-name": false,
"jsdoc-format": true,
"no-consecutive-blank-lines": false,
"no-constructor-vars": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"semicolon": [true, "always"],
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}
If you want you can check the differences between the Angular.io setup (on the left) and the changes I made to make coverage work (on the right) here