How to test Location in angular 2 with karma+jasmine

折月煮酒 提交于 2019-12-12 17:16:34

问题


Angular 2 v.2.0.0 - TS + karma + jasmine.

I test this function - back on click to previously page:

 public isClick: boolean = false;

 public backClicked(location: Location): void {
        if (!this.isClick) {
            this.isClick = true;
            this.location.back();
        }
    }

And this is my test:

describe("NavBarComponent", () => {
    describe("function backClicked(): void", () => {
        let testNavBarComponent: NavBarComponent;
        let loc: Location;
        beforeEach(() => {
            testNavBarComponent = new NavBarComponent(null);
        });
        loc = jasmine.createSpyObj("Location", ["back"]);
        it ("It is backClicked() function test", () => {
            testNavBarComponent.backClicked(loc);
            expect(loc.back).toHaveBeenCalledTimes(1);
        });
    });
});

After i run the test, i've got this error: TypeError: Cannot read property 'back' of null. Maybe problem with createSpyObj, or somethig else?


回答1:


In the backClicked function, you're calling the class instance of location this.location rather than the instance of location passed to the function location. I'm assuming that your NavBarComponent has Location injected due to the error message (by default, things are undefined rather than null).

You could do something like the following:

beforeEach(() => {
    // Mock the location here instead, then pass to the NavBarComponent
    loc = jasmine.createSpyObj("Location", ["back"]);
    testNavBarComponent = new NavBarComponent(loc);
});

Alternatively, I've had good luck using Angular's ReflectiveInjector class. The documentation and articles available for mocking dependencies for tests in Angular 2 is all over the board from having so many iterations of the RC, so I'm not 100% sure this is considered a best practice:

import { ReflectiveInjector } from '@angular/core';
import { Location } from '@angular/common';

describe("NavBarComponent", () => {
    describe("function backClicked(): void", () => {
        let testNavBarComponent: NavBarComponent;
        let loc: Location;

        beforeEach(() => {
            let injector = ReflectiveInjector.resolveAndCreate([
                LocationStrategy,
                Location
            ]);

            // Get the created object from the injector
            loc = injector.get(Location);

            // Create the spy.  Optionally: .and.callFake(implementation)
            spyOn(loc, 'back');

            // Create NavBarComponent with the spied Location
            testNavBarComponent = new NavBarComponent(loc);
        });

        it ("It is backClicked() function test", () => {
            testNavBarComponent.backClicked(loc);
            expect(loc.back).toHaveBeenCalledTimes(1);
        });
    });
});

Edit: This can and should be accomplished using the TestBed.configureTestingModule now: https://blog.thoughtram.io/angular/2016/11/28/testing-services-with-http-in-angular-2.html

Using the ReflectiveInjector, you can also declare your dependencies in the same way you do in app.module. For example, mocking the Http service:

let injector = ReflectiveInjector.resolveAndCreate([
    MockBackend
    BaseRequestOptions,
    {
        provide: Http,
        useFactory: (backend, options) => {
            return new Http(backend, options);
        },
        deps: [MockBackend, BaseRequestOptions]
    }
]);



回答2:


There's confusion in Angular between Location from @angular/common and DOM Location object which is available by default. Inspite of, Angular's version provides .go() function, in fact, it only interacts with router and doesn't reload the page as DOM object do. So, for real browser interaction you have to use the DOM version, which brings you to a problem how to test this?

Unfortunately, it's not possible to spy on because it's writable object. But you can inject it in your component as value. That's how it looks like

export const LOCATION_TOKEN = new InjectionToken<Location>('Window location object');

@Component({
  providers: [
    { provide: LOCATION_TOKEN, useValue: window.location }
  ]
})
export class SomeComponent {
  constructor(@Inject(LOCATION_TOKEN) private location: Location) {}

  useIt() {
    this.location.assign('xxx');
  }
}

See https://angular.io/guide/dependency-injection#non-class-dependencies for details




回答3:


To test in Angular (as in s-f's answer), you can inject the LOCATION_TOKEN into your TestBed, like this:

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


    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [MyComponent],
            providers: [
                { provide: LOCATION_TOKEN, useValue: window.history }
            ]
    })
    .compileComponents();
}));

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

    describe(('goBack()'), () => {

        it('should be clicked', inject([LOCATION_TOKEN], (_loc: Location) => {
            spyOn(_loc, 'back');
            component.goBack();
            expect(_loc.back).toHaveBeenCalledTimes(1);
        }));

    });
});



回答4:


beforeEach(async(() => {
       TestBed.configureTestingModule({
         declarations: [ HeroDetailsComponent ],
        providers: [ MyHeroService ],
         imports: [ RouterTestingModule ],
       providers: [{ provide: Location, useClass: SpyLocation }]
       })
         .compileComponents();
     }));

  it('should logout from application', async(() => {
         const location: Location = TestBed.get(Location);

         expect(location.href).toContain('blablabla url');
   }));


来源:https://stackoverflow.com/questions/41982166/how-to-test-location-in-angular-2-with-karmajasmine

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!