2

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 [];
    // };
  }
});
Mishi
  • 628
  • 4
  • 16
  • 40
  • Try `spyOn(component.orgService, 'currentkhojiOrganizationMessageSource').and.returnValue(of({}))` returnValue should be Observable notice `of` also I guess you need to make `orgService` public in your constructor – Kamran Khatti Sep 28 '20 at 09:36
  • @KamranKhatti firstly if a directly refer as component.orgService by making it public than my test class says that orgService does not exist in class if I replace component.orgService with mocked value (let orgService: any ={ } ) than I get currentOrganizationMessageSource() method does not exist – Mishi Sep 28 '20 at 10:04

3 Answers3

2

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.

AliF50
  • 16,947
  • 1
  • 21
  • 37
1

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.

Deunz
  • 1,776
  • 19
  • 32
0

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.

uiTeam324
  • 1,215
  • 15
  • 34
  • Actually this service KhojiOrganizationService has another service in its constructor. so when I define TestBad like this let service: KhojiOrganizationService = new KhojiOrganizationService(http); and orgService= TestBed.get(service); then I get following error Failed: Uncaught (in promise): Error: StaticInjectorError[[object Object]]: – Mishi Sep 28 '20 at 10:26
  • Define it like this let orgService : KhojiOrganizationService; and then use `TestBed` as mentioned on the answer. – uiTeam324 Sep 28 '20 at 12:15