Ngx-toastr: How to write tests for custom components?

Created on 12 Feb 2018  路  15Comments  路  Source: scttcper/ngx-toastr

Hi,

could you please provide a simple spec.ts example for custom components like 'pink.toast.ts'. I cant figure out how to serve a provider for InjectionToken ToastConfig.

component:

import { Component, OnInit } from '@angular/core';
import { flyInOut } from './toastr.animations';
import { Toast, ToastrService, ToastPackage } from 'ngx-toastr';

@Component({
  selector: '[toastr]',
  styleUrls: ['./toastr.component.css'],
  templateUrl: './toastr.component.html',
  animations: [flyInOut],
  preserveWhitespaces: false,
})

export class ToastrComponent extends Toast implements OnInit {

  private _closeButtonClass: String;

  constructor(
    protected toastrService: ToastrService,
    public toastPackage: ToastPackage,
  ) {
    super(toastrService, toastPackage);
  }

  ngOnInit() {
    this._setButtonOrTimeOut();
    this._setLayoutsByClass();
  }

  private _setButtonOrTimeOut() {
    this.options.disableTimeOut = this.options.closeButton;
    this.options.progressBar = !this.options.closeButton;
    this.options.tapToDismiss = !this.options.closeButton;
  }

  private _setLayoutsByClass() {
    switch (this.toastClasses) {
      case 'toast-success toast':
        this._closeButtonClass = 'btn-success';
        break;
      case 'toast-warning toast':
        this._closeButtonClass = 'btn-warning';
        break;
      case 'toast-error toast':
        this._closeButtonClass = 'btn-error btn-outline';
        break;
      case 'toast-info toast':
        this._closeButtonClass = 'btn-info';
        break;
      default:
        this._closeButtonClass = 'btn-default';
        break;
    }
  }

  private _onCloseButtonClick() {
    this.toastPackage.triggerAction('closed');
    this.remove();
  }
}

test:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Toast, ToastrService, ToastPackage, ToastrModule } from 'ngx-toastr';
import { flyInOut } from './toastr.animations';
import { ToastrComponent } from './toastr.component';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ToastrModule],
      declarations: [ToastrComponent],
      providers: [ToastrService]
    })
      .compileComponents();
  }));

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

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

Error

ToastrComponent should create
Error: StaticInjectorError(DynamicTestModule)[ToastrService -> InjectionToken ToastConfig]: 
  StaticInjectorError(Platform: core)[ToastrService -> InjectionToken ToastConfig]: 
    NullInjectorError: No provider for InjectionToken ToastConfig!

Thank you and best regards
Christoph

Most helpful comment

What happens if instead of importing ToastrModule, you import ToastrModule.forRoot()? The forRoot method provides 4 "services": TOAST_CONFIG (InjectionToken), OverlayContainer, Overlay, and ToastrService. All are required for toasts to work propertly.

Maybe something like this:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ToastrModule.forRoot()],
      declarations: [ToastrComponent],
      providers: [ToastrService]
    })
      .compileComponents();
  }));

All 15 comments

What happens if instead of importing ToastrModule, you import ToastrModule.forRoot()? The forRoot method provides 4 "services": TOAST_CONFIG (InjectionToken), OverlayContainer, Overlay, and ToastrService. All are required for toasts to work propertly.

Maybe something like this:

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ToastrModule.forRoot()],
      declarations: [ToastrComponent],
      providers: [ToastrService]
    })
      .compileComponents();
  }));

Thank you, that solved the problem, but of course another one occured...

beforeEach(async(() => {

    TestBed.configureTestingModule({
      imports: [ToastrModule.forRoot()],
      declarations: [ToastrComponent],
      providers: [ToastrService, ToastPackage]
    })
      .compileComponents();
  }));

now brings this:

Failed: Can't resolve all parameters for ToastPackage: (?, ?, ?, ?, ?, ?).

I think I have to mock this ToastPackage provider, will try this later...

Thanks and regards
Christoph

Hi,

Am facing the same issue Kaffeeserver hes (was) having, tried mocking the toastPackage, but didn't work, any suggestions?

Thanks,
Joel

@kaffeeserver

This is the solution i came up with,

Test:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';

beforeEach(() => {
    TestBed.configureTestingModule({
        imports: [ToastrModule.forRoot()],
        declarations: [ToastrModuleComponent],
        providers: [ToastrModuleService]
    })

.compileComponents();

});

cool

Hello, i encouter the same problem with ToastPackage

@JoelKap Your solution doesn't work for me :/

I am also struggling with mocking toast package. @christophglass were you able to get your test working?

I am also facing this problem.. could we get a full unit test example when extending a Toast?

my custom component looks like

import { Component } from '@angular/core';
import {
  animate,
  keyframes,
  state,
  style,
  transition,
  trigger
} from '@angular/animations';

import { Toast, ToastPackage, ToastrIconClasses, ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-toast',
  templateUrl: './toast.component.html',
  styleUrls: ['./toast.component.scss'],
  animations: [
    trigger('flyInOut', [
      state('inactive', style({
        display: 'none',
        opacity: 0
      })),
      transition('inactive => active', animate('400ms ease-out', keyframes([
        style({
          transform: 'translate3d(100%, 0, 0) skewX(-10deg)',
          opacity: 0
        }),
        style({
          transform: 'skewX(20deg)',
          opacity: 1
        }),
        style({
          transform: 'skewX(-5deg)',
          opacity: 1
        }),
        style({
          transform: 'none',
          opacity: 1
        })
      ]))),
      transition('active => removed', animate('400ms ease-out', keyframes([
        style({
          opacity: 1
        }),
        style({
          transform: 'translate3d(100%, 0, 0) skewX(10deg)',
          opacity: 0
        })
      ])))
    ])
  ]
})
export class ToastComponent extends Toast {

  // default classes for ngSwitch
  toastrIconClasses: ToastrIconClasses = {
    error: 'toast-error',
    info: 'toast-info',
    success: 'toast-success',
    warning: 'toast-warning'
  };

  // constructor is only necessary when not using AoT
  constructor(
    protected toastrService: ToastrService,
    public toastPackage: ToastPackage
  ) {
    super(toastrService, toastPackage);
  }
}

and my unit test looks like

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { ToastComponent } from './toast.component';
import { SharedModule } from 'src/app/shared/shared.module';
import { IndividualConfig, ToastPackage, ToastrModule, ToastrService } from 'ngx-toastr';
import { Globals } from 'src/app/core/globals';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        ToastrModule.forRoot(),
        SharedModule
      ],
      declarations: [
        ToastComponent
      ],
      providers: [
        ToastrService,
        ToastPackage
      ]
    })
      .compileComponents();
  }));

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

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

and im getting the error

Failed: Can't resolve all parameters for ToastPackage: (?, ?, ?, ?, ?, ?).

I m also facing the same

Error: Can't resolve all parameters for ToastPackage: (?, ?, ?, ?, ?, ?). Error: Can't resolve all parameters for ToastPackage: (?, ?, ?, ?, ?, ?). at syntaxError (http://localhost:9876/node_modules/@angular/compiler/fesm5/compiler.js?:215:1) at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata (http://localhost:9876/node_modules/@angular/compiler/fesm5/compiler.js?:10807:1)

I hacked through it and got it to render by stubbing out ToastPackage like this:

@Injectable()
class MockToastPackage extends ToastPackage {
   constructor() {
     const toastConfig = { toastClass: 'customToast' };
     super(1, <IndividualConfig>toastConfig, 'test message', 'test title', 'show', new ToastRef(null));
   }
}

and then the module setup, just provide the mocked toastPackage

TestBed.configureTestingModule({
  imports: [ToastrModule.forRoot()],
  providers: [{ provide: ToastPackage, useClass: MockToastPackage }]
})

I faced with the same problem.
The thing is that ToastPackage is being created only after ToastrService.success (or warning, error) is called.
So, as workaround, I've created a mock test component and use AppToastComponent as entryComponent:

@Component({
    selector: 'clp-test',
    template:" <div></div>",
})
class TestComponent {
}

beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [
                TestComponent,
                AppToastComponent,
            ],
            schemas: [NO_ERRORS_SCHEMA],
            imports: [
                ToastrModule.forRoot({
                    disableTimeOut: true,
                    toastComponent: AppToastComponent,
                    tapToDismiss: false,
                    progressBar: true,
                    onActivateTick: true,
                }),
                NoopAnimationsModule,
            ],
        })
            .overrideModule(BrowserDynamicTestingModule, {
                set: {
                    entryComponents: [AppToastComponent],
                },
            })
            .compileComponents();
    }));

a bit upgraded @avoerman's solution:

import { Injectable, NgModule } from '@angular/core';
import { IndividualConfig, ToastPackage, ToastRef, ToastrModule } from 'ngx-toastr';


@Injectable()
class MockToastPackage extends ToastPackage {
  constructor() {
    const toastConfig = { toastClass: 'custom-toast' };
    super(1, <IndividualConfig>toastConfig, 'test message', 'test title', 'show', new ToastRef(null));
  }
}

@NgModule({
  providers: [
    { provide: ToastPackage, useClass: MockToastPackage }
  ],
  imports: [
    ToastrModule.forRoot(),
  ],
  exports: [
    ToastrModule
  ]
})
export class ToastrTestingModule {

}

so in TestBed you just need this ToastrTestingModule:

TestBed.configureTestingModule({
  imports: [ToastrTestingModule],
})

@AndreiShostik I'm based on your solution and made this. This solution of problem don't need additional module.

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToastPackage, ToastRef, ToastrModule } from 'ngx-toastr';

import { ToastrComponent } from './toastr.component';

describe('Component: ToastrComponent', () => {
    let component: ToastrComponent;
    let fixture: ComponentFixture<ToastrComponent>;
    let toastPackageMock: {
        toastId: number;
        toastType: string;
        afterActivate: jasmine.Spy;
        config: { toastClass: string; };
        message: string;
        title: string;
        toastRef: ToastRef<unknown>;
    };

    beforeEach(() => {
        initMockProviders();
        TestBed.configureTestingModule({
            declarations: [ToastrComponent],
            imports: [
                BrowserAnimationsModule,
                ToastrModule.forRoot(),
            ],
            providers: [
                { provide: ToastPackage, useValue: toastPackageMock }
            ],
            schemas: [NO_ERRORS_SCHEMA]
        });

        fixture = TestBed.createComponent(ToastrComponent);
        component = fixture.componentInstance;
    });

    afterEach(() => {
        fixture.destroy();
    });

    it('should create an instance', () => {
        fixture.detectChanges();

        expect(component).toBeTruthy();
    });

    function initMockProviders(): void {
        toastPackageMock = {
            toastId: 1,
            toastType: 'success',
            afterActivate: jasmine.createSpy('afterActivate'),
            config: { toastClass: 'custom-toast' },
            message: 'test message',
            title: 'test title',
            toastRef: new ToastRef(null)
        };
    }
});

Not sure if it helps someone, but it turned out that I had two TestBeds in one file

@yarrgh thanks your solution worked for me

Thanks @AndreiShostik your solution worked for me

Was this page helpful?
0 / 5 - 0 ratings