Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error: <spyOn> : fromEvent is not declared writable or has no setter

Tags:

The old code uses rxjs v5.5.12, We copied the same code to our new project which uses rxjs v6.4.0. We are getting this error when we tried to run the test case.

Old Code:

import * as ObservableEvents from 'rxjs/Observable/fromEvent'; spyOn(ObservableEvents, 'fromEvent').and.returnValue(new Subject<any>().asObservable()); 

New Code:

import * as rxjs from 'rxjs'; spyOn(rxjs, 'fromEvent').and.returnValue(new Subject<any>().asObservable()); 

In both cases we are getting this error:

Error: : fromEvent is not declared writable or has no setter

We couldn't find a valid resource to solve this issue.

Update #1

We tried using

import * as rxjs from 'rxjs'; spyOn(jasmine.createSpyObj(rxjs), 'fromEvent').and.returnValue(new Subject<any>().asObservable()); 

but this time, we got

createSpyObj requires a non-empty array or object of method names to create spies for thrown

Update #2:

We used the code from @Omair-Nabiel, now getting a new error

      TypeError: Object(...) is not a function           at XxxPopoverDirective.fromEvent [as createPopover] (http://xxx:xxxx/src/app/shared/xxx/xxx.directive.ts?:113:37)           at XxxPopoverDirective.createPopover [as mouseClick] (http://xxx:xxxx/src/app/shared/xxx/xxx.directive.ts?:70:14)           at runTest (http://xxx:xxxx/src/app/shared/xxx/xxx.directive.spec.ts?:181:19) 

xxx.directive.ts

line 113-> this.componentRef && this.componentRef.destroy(); this.componentRef = null;  line 70-> constructor( ... private resolver: ComponentFactoryResolver, ...   ) { } 

Update #3

Hi Omair Nabiel, Please find the below code we are using, please let me know the solution,

file="popover.directive.ts" Code:

import { fromEvent } from 'rxjs/Observable/fromEvent';  this.clickOutSub = fromEvent(this.documentRef.getDocument(), 'click').subscribe(this.clickOut.bind(this));  file="popover.directive.spec.ts" Code: import * as ObservableEvents from 'rxjs/Observable/fromEvent';  function runTest() {  spyOn(ObservableEvents, 'fromEvent').and.returnValue(new Subject<any>().asObservable());   }  it('...', () => { expect(ObservableEvents.fromEvent).toHaveBeenCalled(); }); 
like image 678
Raju Avatar asked Jul 26 '19 06:07

Raju


People also ask

What is spyon error in Jasmine?

Error: <spyOn> : assign is not declared writable or has no setter Here’s the typical Jasmine error, if you try to do it this way. Instead of manipulation location directly we need to inject it our component with Dependency injection mechanism.

Is the name of bean class property is not writable?

Bean property is not writable or has an invalid setter method. Stuck with the error. Any suggestion. Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'name' of bean class: Bean property 'name' is not writable or has an invalid setter method.

What is causation caused by Bean property notwritablepropertyexception?

Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'name' of bean class: Bean property 'name' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter? It seems that you injecting dependencies of a class.


2 Answers

You need to spy on a property of rxjs. Using spyOnProperty will solve the error. Try this

 import * as rxjs from 'rxjs'  import { of, fromEvent } from 'rxjs';    spyOnProperty(rxjs, 'fromEvent').and.returnValue(of({})) 

you can also add to getter/setters using this like

spyOnProperty(rxjs, 'fromEvent', 'get').and.returnValue(false) 

Hope this helps

like image 86
Omair Nabiel Avatar answered Sep 20 '22 21:09

Omair Nabiel


The problem is that a module namespace object like import * as rxjs has a specific behavior and doesn't allow to mutate itself in many cases. Here's a few relevant links:

  • Cannot spy on individual functions that are individually exported
  • Mocking fromEvent in rxjs6
  • Spy on jasmine function from an Angular library is not working
  • import * as (jasmine spyOn) not writable
  • Can webpack 4 modules be configured as to allow Jasmine to spy on their members?

As you can see it's a well known issue but at the moment there's no ultimate solution that works in all cases. The most common workaround is to use spyOnProperty like Alejandro Barone's answer suggests, I've tried this solution in Angular 8 / TypeScript 3.4 setup and it works well, but in doesn't work in Angular 10 / TypeScript 4 and gives the following error:

fromEvent is not declared configurable

But let's look at the problem from a different angle. As an example you can imagine a class that subscribes to window's resize event and increments some counter when the event is triggered. The class can use fromEvent to subscribe to the event or can subscribe directly via window.addEventListener. In both cases the class will behave the same - the counter will be incremented when the event happens. By spying on fromEvent you make an assumption that the class uses that function, however the only contract the class gives you is its interface. In the future someone might decide to use window.addEventListener instead of fromEvent and the tests will be broken despite the class works the same way. So the right way to test such a class is to trigger window's resize event and check that the counter is incremented. It's a good practice to test classes like black boxes without any assumptions about its implementation.

If it's still important to you to spy on fromEvent function, you can create a wrapper on it and mock it in your tests, for example:

import { fromEvent, Observable, of } from 'rxjs'; import { FromEventTarget } from 'rxjs/internal/observable/fromEvent';  class EventObserver {   observe<T>(target: FromEventTarget<T>, eventName: string): Observable<T> {     return fromEvent(target, eventName);   } }  class MyClass {   constructor(     private eventObserver = new EventObserver()   ) {}    doSomething() {     this.eventObserver.observe(window, 'resize').subscribe(() => {       // do something     });   } }  it("#doSomething should subscribe to window's resize event", () => {   const eventObserver = jasmine.createSpyObj<EventObserver>(EventObserver.name, ['observe']);   eventObserver.observe.and.returnValue(of({}));    const myClass = new MyClass(eventObserver);   myClass.doSomething();    expect(eventObserver.observe).toHaveBeenCalledTimes(1);   expect(eventObserver.observe).toHaveBeenCalledWith(window, 'resize'); }); 
like image 40
Valeriy Katkov Avatar answered Sep 20 '22 21:09

Valeriy Katkov