Jasmine trying to read property that doesn't exist - javascript

I have an extremely simple component:
import { Component } from '#angular/core';
#Component({
selector: 'loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.scss']
})
export class LoadingComponent {
}
which just displays a static loading logo. No interactivity at all. I wrote a test just to test its instantiation:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { LoadingComponent } from './loading.component';
describe('LoadingComponent', () => {
let component: LoadingComponent;
let fixture: ComponentFixture<LoadingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoadingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoadingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should exist', () => {
expect(component).toBeTruthy();
});
});
In Firefox, this test throws an error: j is undefined, and in Chromium browsers I see Cannot read property 'startTime' of undefined. The template is simply:
Sample Text
for the sake of figuring out what's going on. So there's no mention of 'startTime' anywhere in those three files - what's going on??
Note
I do use startTime properties in other components, but I can't figure out how/why that would matter.
Edit
I've been able to get this error to disappear by just adding:
afterAll(() => {
TestBed.resetTestingModule();
});
to every single test in the project. I'm leaving the question open because I have no idea:
Why this works/what the underlying problem was
If I'm swinging a heavier axe than I need to - would adding it to only one test suffice?

The error you are seeing is definitely not coming from component and spec file you have posted. The error you are seeing is most likely coming from a component that does an async operation and the error occurs after the spec that instantiated it has already finished.
First off put an f in frontof the describes of this spec file.
fdescribe('LoadingComponent', () => {
This will cause the spec runner to only run this file. You will see that it passes fine.
Next you want to remove the f and the search your project for startTime, try running specs for components that use the property startTime one at a time.
If you can't locate it then put an x in front of the describes of them one at a time, eventually you will find which one is causing the error as it wont happen when you have an x on the spec that causes the error.
Any specs that do async function should use the async function so that it waits for all async activity to finish before moving on to the next spec.

Related

How can I reset a statically imported module in favour of a dynamically imported one, in mocha tests?

I have written a javascript file that provides configuration, and separately a proxy that lets me provide a fallback value if the property access was unsuccessfull
// stackUrlMap.js
export const stacks = {
'stackoverflow.com': 'StackOverflow',
'cooking.stackexchange.com': 'SeasonedAdvice',
}
// stackUrlConfig.js
import { stacks } from './stackUrlMap';
const getOrParse = parser => ({
get: (target, prop) => target?.[prop] ?? parser(prop);
})
const urlToName = url => url.split('.')[0];
export const stackUrlToNameMap = new Proxy(stacks, getOrParse(urlToName));
This was working perfectly, and I had a test written for it that passed.
import { expect } from 'chai';
import sinon from 'sinon';
import * as urlMap from './stackUrlMap';
describe('stackUrlConfig.js', function() {
let sandbox;
let stackUrlToNameMap = {};
beforeEach(async function() {
sandbox = sinon.createSandbox();
sandbox.stub(urlMap, 'stack').value({'madeup.url.com': 'Made Up Stack'});
// import here so we get the stubbed values passed through.
({ stackUrlToNameMap } = await import('./stackUrlConfig.js'))
});
it('can get an existing prop from the proxied map', function() {
const field = 'madeup.url.com';
// stub working correctly when accessed from test file
expect(urlMap.stacks[field]).to.exist();
// tests that if the proxied map can find the prop it uses that value
expect(stackUrlToNameMap[field]).to.equal('Made Up Stack');
});
})
However, now I've written code that uses stackUrlConfig.js elsewhere, that is imported by my test setup elsewhere, and the dynamically imported proxied map stackUrlToNameMap in the beforeEach of my test is not being 'respected', and the real stacks object is being used for the lookup, meaning I get this AssertionError:
AssertionError: expected 'madeup' to equal 'Made Up Stack'
If I put a console.trace in the stackUrlConfig.js, I can see it's first called before my test runs as it's imported into another file that isn't run by mocha yet.
I don't want to have to use decache (I'm not even sure it would work, I'm not using require) as seen in Re-importing modules between mocha tests if I can avoid it.
I took the code and tried to add a cache busting, like so:
const absolutePath = path.resolve(__dirname, './stackUrlConfig.js');
({ stackUrlToNameMap } = await import(`${absolutePath}?update=${Date.now()}`));
But my mocha setup did not find the resulting module.
I can't control whether this file is imported elsewhere first in my test, so can I reset mocha's reference to the module somehow?

What could cause an "update to ... inside a test was not wrapped in act" despite using act?

I'm doing my first steps with testing React functional components with something like:
import React from 'react';
import { render } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import App from './App';
test('basic app rendering', () => {
act(() => {
render(<App />);
})
});
However this is resulting in the infamous warning that we have to use act:
Warning: An update to SomeSubComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
The link brings me back to where I'm coming from, and basically shows an example that is using act exactly like I do.
I see a few possibilities:
Something is wrong with my usage of act.
Something is wrong with my test setup / configuration (I'm using basically a plain create-react-app typescript template).
Something is wrong with the component I'm testing.
Any ideas what could lead to this situation that I get the "not wrapped in act" warning despite already wrapping in act?
It turned out that I simply needed this:
test('basic app rendering', async () => {
await act(async () => {
render(<App />);
})
});
This has been raised as an issue in version 16.8. Similar to that linked issue, my problem was an async function in useEffect.
In versions 16.9+ onwards (I'm on 17.0) an async version of act is available.
The reason I had asked the question is that I first made a stupid mistake trying the async variant:
test('basic app rendering', async () => {
await act(() => { // note missing async here
render(<App />);
})
});
I.e., I forgot to annotate the function passed to act as async as well. In this case it doesn't work (and in fact there is a warning 'await' has no effect on the type of this expression.).

Testing Material Design Angular components with jasmine

Edit2: Even more specific, using something like Material Button works fine. The Side Nav does not work well with the setup below, however.
Edit: I did more testing and found this is specifically related to Angular Material, as these errors don't occur without it. I'm still unsure how to fix it.
I'm trying to set up some basic tests for a new component, but I keep running into error after error. Specifically, after adding #angular/material to the project. The current error is:
Error: Found the synthetic listener #transform.start. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.
Which feels like a red herring to me.
Here is the spec:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { MatSidenavModule } from '#angular/material/sidenav';
import { AngularNavigationComponent } from './angular-navigation.component';
describe('AngularNavigationComponent', () => {
let component: AngularNavigationComponent;
let fixture: ComponentFixture<AngularNavigationComponent>;
beforeEach(
async(() => {
TestBed.configureTestingModule({
declarations: [AngularNavigationComponent],
imports: [RouterTestingModule, MatSidenavModule]
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(AngularNavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
And here is the component
import { Component, OnInit } from '#angular/core';
import { MatSidenavModule } from '#angular/material/sidenav';
import { RouterLink } from '#angular/router';
#Component({
selector: 'mysupercustomcat-navigation',
templateUrl: './angular-navigation.component.html',
styleUrls: ['./angular-navigation.component.scss']
})
export class AngularNavigationComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
Can anyone tell me what I'm doing wrong, and also perhaps explain declarations vs. imports in the Testbed configuration area?
Edit: This is a hybrid app if that matters
I've added two key sections to avoid errors like this:
import { NO_ERRORS_SCHEMA } from '#angular/core';
is added at the top of the page and:
schemas: [NO_ERRORS_SCHEMA],
is added at the same level as declarations.
What does this do? This tells angular not to error on unknown elements or attributes. Now this works FOR THIS USE CASE because I am not super interested in integration testing. I believe that might cause problems in this case.
I am interested in unit testing, and ignoring template attributes is fine with me. Adding this lets me get to that level.
This answer is as complete as I could get it for the moment.

Angular - How to implement Exception Handling on component level

While working inside Angular (Angular 4, 5), if a component raises Error (TypeError or null or undefined error or so), whole application breaks onward.
How can we deal with this, to catch errors on component level and possibly show a fallback UI, like React16 does using Error Boundaries.
I would approach it by handling the error at Component level and have a service that listens to any errors happening at Component or Service level.
Ex:
Throw the error from the service
catch the error in component
Handle the error, process it and send the Error event with details to ErrorService.
You can have a app level component "errorBannerComponent" which takes input from ErrorService and paint your UI.
As soon as the error is received in ErrorService, The errorBannerComponent should display the error on screen.
Hope it helps.
Also By default, Angular comes with its own ErrorHandler that
intercepts all the Errors that happen in our app and logs them to the
console, preventing the app from crashing. We can modify this default behavior by creating a new class that implements the ErrorHandler:
You can find more details and example here:
As the proposed solutions are rather dull. I tried to recreate it myself. The easiest solution would be to provide a module scoped custom ErrorHandler class.
Thanks to this, you could even create a multiple different ErrorBoundaries.
My proposed solution can be seen here: https://stackblitz.com/edit/angular-ivy-brb143?file=src/app/widget/widget.module.ts
What is really important for this solution to work (atleast it didn't work otherwise for me). Was to provide the custom error handler as a part of a module rather than a component directly.
The important bits from the solutions:
module:
/**
* This is really imporant as this allows us to provide a module scoped ErrorHandler
*/
#NgModule({
imports: [CommonModule],
declarations: [WidgetComponent],
providers: [{ provide: ErrorHandler, useClass: WidgetErrorHandler }],
exports: [WidgetComponent],
})
export class WidgetModule {}
component where we can throw, and catch error
#Component({
selector: 'app-widget',
templateUrl: './widget.component.html',
styleUrls: ['./widget.component.css'],
})
export class WidgetComponent implements OnInit {
constructor(#Inject(ErrorHandler) public widgetError: WidgetErrorHandler) {}
ngOnInit() {
this.widgetError.isError$.subscribe((error) =>
console.log('component can act on error: ', error)
);
}
public handleThrowErrorClick(): void {
throw Error('Button clicked');
}
}
and the handler iself
#Injectable()
export class WidgetErrorHandler implements ErrorHandler {
public isError$: Subject<Error | any> = new Subject();
handleError(error) {
console.log('Intercepted error', error);
this.isError$.next(error);
}
}

Angular 2. Error: Loading chunk failed

Using angular 2 with lazy loaded modules, I can receive(for example) 401 HTTP code from server
bootstrap 0b40fee…:101 GET http://localhost:8082/2.chunk.js
Error: Loading chunk 2 failed.
at HTMLScriptElement.onScriptComplete (bootstrap 0b40fee…:91)
at HTMLScriptElement.wrapFn (zone.js:1032)
at ZoneDelegate.invokeTask (zone.js:414)
at Object.onInvokeTask (core.es5.js:4119)
at ZoneDelegate.invokeTask (zone.js:413)
at Zone.runTask (zone.js:181)
at HTMLScriptElement.ZoneTask.invoke (zone.js:476)
How to handle this error?
Check my answer for details
Workaround to bypass this chunk fails error => Programmatically force app to reload if chunks failed error occurs using global error handler.
import { ErrorHandler } from '#angular/core';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error: any): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
if (chunkFailedMessage.test(err.message)) {
window.location.reload();
}
}
}
Provide it in our root module to change default behavior in our app, so instead of using default ErrorHandler class we are using our custom GlobalErrorHandler class.
#NgModule({
providers: [{provide: ErrorHandler, useClass: GlobalErrorHandler}]
})
I was having the same problem so I investigated. I found the solution. This happened to me when I redeployed to another server and the chunk had a [hash].
You can catch the error either in a catch all like this:
ngOnInit() {
if (!this.previousRouterErrorHandler) {
this.previousRouterErrorHandler = this.router.errorHandler;
let that = this;
this.router.errorHandler = function (err: any) {
// Handle here. err.message == "Loading chunk chunk-name failed."
return that.previousRouterErrorHandler.apply(that.previousRouterErrorHandler, arguments);
};
}
}
Or directly at the link which navigated
click() {
this.router.navigate(['/lazy-route'])
.catch(err => {
// Handle here
});
}
Here is my solution for this. I inject this service as a singleton in my app / core module.
It waits for instances of NavigationError from the router, checks if they are ChunkLoadError's and then does a redirect to the place the user wanted to go to anyway.
// Angular
import { Injectable, OnDestroy } from '#angular/core';
import { Router, NavigationError } from '#angular/router';
// Rxjs
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
#Injectable()
export class ChunkErrorHandler implements OnDestroy {
private subscription: Subscription;
constructor(router: Router) {
this.subscription = router.events
.pipe(filter(event => event instanceof NavigationError))
.subscribe(event => {
this.handleRouterErrors(event as NavigationError);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
private handleRouterErrors(event: NavigationError) {
if (event.error.name === 'ChunkLoadError') {
window.location.href = `${window.location.origin}${event.url}`;
}
}
}
It happen when when deploy new code.The manifest.js which holds the files and hashes doesn't update without refreshing and when it loads a chunk it obviously uses the old hash from manifest.js.
So while catching error we can do force reload with given url :-
click() {
this.router.navigate(['/lazy-route'])
.catch(err => {
// Handle here
// reload with given route
// window.location.pathname('/lazy-route');
// OR
// reset existing route(containing query params) with given route and force reload
window.history.pushState({}, document.title, '/lazy-route' );
window.location.reload();
});
}
chunk related errors can be raised by any environment or routing related issues making them hard to debunk.
In my case, the amount of data moving in my PWA was too much to handle by the angular router. It was flooding the headers of the js chunks getters and therefore raising bad_request errors.
I suggest you to check out those network calls (getters of chunks.js like http://localhost:xxxx/158.js) for anything unusual in headers and refactor sketchy stuff in your current dev environment, since it's a real black hole time to investigate the source of the error by yourself.
Hope that'll help
check out Catch Storage, i guess service worker save some thing in catch storage
console.log(Object.entries(error));
this help me to understand what's inside the error is
rejection,
promise,
zone,
task
and below is my solution:
handleError(error) {
switch (error?.rejection?.name) {
case 'ChunkLoadError':
window.location.href = window.location.href;
break;
default:
break;
}
}
In my case, I was putting my files in an S3 bucket. I kept getting this error because it was calling the wrong filenames all together and returning an html error response.
At some point I let the IT team know what was happening. They were like, let's invalidate the cache on CloudFront... What?! Yeah! Let's do that...
Moral of the story, if you've been searching the web for answers to this error and can't find any, check with the IT team or any place that the index.html file might be getting cached.
this probably means unhandled exception. you have to handle error responses (4xx, 5xx status codes) from server in whatever way you want: show error message somewhere, redirect to some page, do anything but not leave it unhandled.
for example:
return this.http.get(requestDetails)
.map(res => res.json())
.catch(err => {
console.log('server error:', err)
Observable.throw(err);
});

Categories