0
votes

Before I begin, I have to say that I am VERY new to testing in Jasmine. It's one of these things that I didn't pay much attention or effort to when I was learning, and now I regret it.

Anyway, I'm writing unit testing for one of my components. Said component has a visible boolean, initialized as false. When said boolean property becomes true, the component's display css attribute goes from none to block, making it visible.

Since my component is a child component, the cycle that I built so that a parent component technically 'triggers' the component is the following:

  • The component initializes in an insivible state. Both the parent and the child are connected to a Service looking like this:
    export class SidebarService {

    public toggleSidebar: EventEmitter<void> = new EventEmitter();

      constructor() {}

    public showSidebar() {
       this.toggleSidebar.emit();
      }
    }

The service does not require connection to any endpoints nor does it return any data: the emitter acts as a 'one-time-per-click' signal to the child component.

  • Clicking a button in the parent template, the parent calls the following method:
 toggleSidebar() {
    this.sidebarService.showSidebar();
  }
  • On my child component's OnInit, I wrote the following:
     ngOnInit() {
        this.sidebarService.toggleSidebar.subscribe(() => {
          this.visible = true;
        });
      }

So that when the parent triggers the event emitter, the child receives it and changes its visible property. The child component has its own manual method to reset the visible property to false.

From a functional perspective, it does the job: the component 'appears' when the parent component clicks the button. But now I fear I might have botched the 'connectivity' aspect of their relation.

When writing the unit test, what I have so far for the component is the following:

describe('FilterSidebarComponent', () => {
  let component: FilterSideBarComponent;
  let fixture: ComponentFixture<FilterSideBarComponent>;

  const sidebarServiceSpy = jasmine.createSpyObj('SidebarService', [
    'showSidebar'
  ]);

  const fb: FormBuilder = new FormBuilder();

  configureTestSuite(() => {
    TestBed.configureTestingModule({
      imports: [
        MockModule(FormsModule),
        MockModule(ReactiveFormsModule),
        MockModule(TranslateModule.forRoot())
      ],
      declarations: [
        FilterSideBarComponent,
        CheckboxInputComponent,
        MockComponent(CountrySingleSearchComponent),
        MockComponent(UniversitiesSearcherComponent)
      ],
      providers: [
        {provide: SidebarService, useValue: sidebarServiceSpy},
        {provide: FormBuilder, useValue: fb},
      ]
    });
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(FilterSideBarComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

And the only test written so far ('should create'), fails, with Jasmine telling me: TypeError: Cannot read property 'subscribe' of undefined at at FilterSideBarComponent.subscribe [as ngOnInit]

I have searched for similar topics in StackOverflow regarding ways of mocking the subscription of services, but all examples I have found use services that return objects, data or Observables.

Of course I can rewrite the Service as long as it conserves its function, but- is there an effective way to test a 'Service' like mine, or should I rewrite the service in some way?

1

1 Answers

1
votes

The issue is that you're subscribing to the toggleSidebar property which isn't defined in your mock service. I'm not sure Jasmine supports mocking properties so I would just create a mock object something like the below. When you want to trigger your event for testing you'd then do sideBarSubject.next().

Also I don't believe it is recommended to use EventEmitters in services. I would change your service to use a Subject as well.

If you later need to spy on your new mock sidebar service you can do spyOn(sidebarServiceSpy, 'showSideBar') which is necessary because you're no longer creating it as a spy to begin with.

describe('FilterSidebarComponent', () => {
  let component: FilterSideBarComponent;
  let fixture: ComponentFixture<FilterSideBarComponent>;


  let sideBarSubject = new Subject<void>();
  const sidebarServiceSpy = {
    toggleSidebar = sideBarSubject;
    showSidebar: Function(){}
  }

  const fb: FormBuilder = new FormBuilder();

  configureTestSuite(() => {
    TestBed.configureTestingModule({
      imports: [
        MockModule(FormsModule),
        MockModule(ReactiveFormsModule),
        MockModule(TranslateModule.forRoot())
      ],
      declarations: [
        FilterSideBarComponent,
        CheckboxInputComponent,
        MockComponent(CountrySingleSearchComponent),
        MockComponent(UniversitiesSearcherComponent)
      ],
      providers: [
        {provide: SidebarService, useValue: sidebarServiceSpy},
        {provide: FormBuilder, useValue: fb},
      ]
    });
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(FilterSideBarComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});