How to mock property of a service class in angular (Jasmine/Karma)

Viewed 4863

I am trying to mock currentOrganizationMessageSource from my service but getting getting following error:

this.OrganizationService.currentOrganizationMessageSource.subscribe is not a function

I tried to use spyOnProperty and than I get currentOrganizationMessageSource is not a property

Class:

export class OrganizationEditComponent implements OnInit {

  feedback: any = {};
  appOrgs: OrganizationDataModel;
  organizationLocationTableTitle: string;


  constructor(
    public route: ActivatedRoute,
    private router: Router,
    private orgService: OrganizationService) {
  }


  ngOnInit() {
    this.loadOrganisation();
  }

  private loadOrganisation()
  {
    this.orgService.currentOrganizationMessageSource.subscribe(
      org => this.appOrgs = org);
      this
      .route
      .params
      .pipe(
        map(p => p.id),
        switchMap(id => {
          if (id === 'new') { return of(this.appOrgs); }
          if (id != null) {
            return this.orgService.findByOrganizationsId(id);
          }
        })
      )
      .subscribe(orgs => {
          this.orgService.changeMessage(orgs);
          this.appOrgs = orgs;
          this.feedback = {};
        },
        err => {
          this.feedback = {type: 'warning', message:'Error'};
        }
      );
  }

Service:

export class OrganizationService {

  private OrganizationMessageSource = new BehaviorSubject( new OrganizationDataModel());
  currentOrganizationMessageSource = this.OrganizationMessageSource.asObservable();


  changeMessage(appOrganization: appOrganizationDataModel) {
    this.appOrganizationMessageSource.next(appOrganization);
  }
}

Test spec class:

fdescribe('OrganizationEditComponent', () => {
  let component: OrganizationEditComponent;
  let fixture: ComponentFixture<OrganizationEditComponent>;
  let activatedRoutes: any = {};


  beforeEach(async(async () => {
    const OrganizationService: OrganizationService = jasmine.createSpyObj('OrganizationService', ['currentOrganizationMessageSource']);
    await TestBed.configureTestingModule({
      declarations: [ OrganizationEditComponent ],
      providers: [
        { provide: ActivatedRoute, useValue: activatedRoutes },
        { provide: Router, useValue: {url: ''}},
        { provide: OrganizationService, useValue: OrganizationService },
      ],      
      imports: [ FormsModule ] ,
      schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
    })
    .compileComponents();
    // component.ngOnInit();
    fixture = TestBed.createComponent(OrganizationEditComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }));

  function givenServicesAreMocked() {
    const spy = TestBed.get(OrganizationService);
    // let mockService = jasmine.createSpyObj(OrganizationService, ['currentOrganizationMessageSource']);
    // mockService.currentOrganizationMessageSource.and.returnValue('');
    // spyOnProperty(OrganizationService, "OrganizationMessageSource").and.returnValue([]);
    // OrganizationService.currentOrganizationMessageSource = () => {
    //   return [];
    // };
  }
});
3 Answers

You have the right idea but you can't mock properties with createSpyObj, just methods.

import { of } from 'rxjs';
....
// the 2nd argument is for public methods, not properties
    const OrganizationService: OrganizationService = jasmine.createSpyObj('OrganizationService', ['changeMessage']);
// attach currentOrganizationMesageSource as a property to OrganizationService and mock the value
OrganizationService.currentOrganizationMessageSource = of(/* Mock the value here */);
// keep everything else the same

That should hopefully work.

You also need to properly mock the ActivatedRoute and the Router. For the router, just add RouterTestingModule to the imports array. It's provided by Angular and helps when testing component/services that inject the router.

You can try with this createMockService method, based on ts-mockery lib

import { Mock, Mockery } from 'ts-mockery';

interface Stub {
  [key: string]: any;
}

export function createMockService<T>(service: Type<T>, stubs?: Stub) {
  Mockery.configure('jasmine');
  const mockedFunctions = stubs ? stubs : {};
  if (!stubs) {
    for (const key in service.prototype) {
      mockedFunctions[key] = () => null;
    }
  }
  return {
    provide: service,
    useValue: Mock.of<Type<T>>(mockedFunctions)
  };
}

Then you can use it like this

beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [SomeModule, OtherModule],
      declarations: [SomeComponent, MockComponent(OtherComponent)],
      providers: [createMockService(OrganizationService)]
    }).compileComponents();
  }));

This has nothing to do with your problem but note the MockComponent from ng-mocks lib which is a proper way to encapsulated your karma tests, thus enabling testing only for your component, without using [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA] which is kinda bad practice.

Here is an exemple where you need the returned value mocked using the of rxjs operator

import { of } from 'rxjs';
const RESULT = [{id: 1, name:"test"},{id: 2, name:"toto"}];
beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [SomeModule, OtherModule],
      declarations: [SomeComponent, MockComponent(OtherComponent)],
      providers: [createMockService(OrganizationService, { yourMethod: () => of(RESULTS) })]
    }).compileComponents();
  }));

Your mocked service will always be very easy to mock that way.

Use of operator.

import { of } from 'rxjs';

Define orgService before use:

 let orgService : KhojiOrganizationService;

add this to beforeach:

orgService= TestBed.get(KhojiOrganizationService);

Test Case:

it('should fetch data', () => {
   spyOn(orgService, 'currentkhojiOrganizationMessageSource').and.returnValue(of('test Data'));
   component.ngOnInit();
   expect(component.appOrgs).toEqual('test Data');
});

As an example i am passing string inside of operator. You can pass Object/ array of objects inside that and change the expectation as per your datamodel.

Related