3

Using TestBed, we are able to create mock classes for classes that are available with dependency injection. For example, MyButtonClass has access to ElementRef and MyService since they are implemented with dependency injection, and so we can override them. The problem I have is that, to write a Jasmine test, I have to create mock classes to override methods of classes that are not accessed with dependency injection.

In this case, ScriptLoader.load will load ThirdPartyCheckout in the global space. This means, it might not be available when Jasmine reads what is inside the subscribe operator. For this reason, I would like to mock the former first and then the latter after. Or maybe there is a different way to get around this.

It would be great if someone can suggest a way to create mock classes to override the ScriptLoader.load method and ThirdPartyCheckout.configure method.

The directive to be tested:

@Directive({
    selector: '[myButton]'
})
export class MyButtonClass implements AfterViewInit {

    private myKey: string;

    constructor(private _el: ElementRef, private myService: MyService) {}

    ngAfterViewInit() {
        this.myService.getKey()
            .then((myKey: string) => {
                this.myKey = myKey;
                ScriptLoader.load('https://thirdpartyurl.js', this._el.nativeElement)
                    .subscribe(
                        data => {
                            this.handeler = ThirdPartyCheckout.configure(<any>{
                                key: this.myKey
                                // etc
                                // and some methods go here
                            });
                        },
                        error => {
                            console.log(error);
                        }
                    );
        });
    }
}

Here is the test code:

@Component({
    selector: 'test-cmp',
    template: ''
})
class TestComponent {}

class mockMyService {
    getKey() {
        return Promise.resolve('this is a key in real code');
    }
}

describe('myButton', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent, MyButtonClass],
            providers: [
                {provide: MyService, useClass: mockMyService}
            ]
        });
    });

    describe('ngAfterViewInit', fakeAsync(() => {
        const template = '<div><div myButton></div></div>';
        TestBed.overrideComponent(TestComponent, {set: {template: template}});
        let fixture = TestBed.createComponent(TestComponent);
        fixture.detectChanges();
        tick();
    }));
});
jayscript
  • 319
  • 2
  • 5
  • 13

1 Answers1

4

Functions being first-class citizens, you can just assign a new function to it

let originalFn;

beforeEach(() => {
  originalFn = ScriptLoader.load;
});

afterEach(() => {
  ScriptLoader.load = originalFn;
});

it('...', fakeAsync(() => {
  ScriptLoader.load = (url, el: Element): Observable<string> => {
    return Observable.of('HelloSquirrel');
  };
  ...
}));

Other than this, you might want to just consider using DI. One of the main reasons for using DI is for better testability. For the ScriptLoader just make the method a non static method, and for the third party lib just create as abstraction service layer for it.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I simply overwritten `ScriptLoader.load` inside `beforeEach()` and worked. Thanks. But, what you are doing with `beforeEach` and `afterEach` is that every `it` overrides ScriptLoader.load every time and brings that back to an empty variable, correct? What is the point of that? Perhaps, I haven't distinguished the difference between overwriting a static method and assigning a new function to a function? – jayscript Sep 15 '16 at 02:22
  • Just to reset it so you can use a different method in each test if you want. If you wanted it the same for all tests, you can use `beforeAll` and `afterAll`. Remember that static methods stay with the class, not the instance. So changing it in this test would affect even other test (files) using it. So we want to reset it back to the original method before finishing our tests – Paul Samsotha Sep 15 '16 at 02:33
  • _" I haven't distinguished the difference between overwriting a static method and assigning a new function to a function?"_ - not sure what you mean by overwriting. I think what you are referring to is the same thing – Paul Samsotha Sep 15 '16 at 02:34
  • How does assigning a new function to a static method affect other tests (files)? I thought It can be overwritten within the scope of a single test. That is, other tests (files) will not see the overwritten method. They will just refer to the original static method that is intact. Am I wrong? – jayscript Sep 15 '16 at 02:56
  • Static methods are "class scoped". Meaning it stays the same for the life of the class. If you change it, as long as that class is alive, it will stay changed. No matter what file it's being imported into. You can test it. Get two different test files and change a static variable in a (in the first file that executes) and you should see in the second file that's run that the value stays changes. This is different from how "instance" variables work, where each _instance_ gets it's own value – Paul Samsotha Sep 15 '16 at 02:59