How to stub a method of jasmine mock object?

99,959

Solution 1

You have to chain method1, method2 as EricG commented, but not with andCallThrough() (or and.callThrough() in version 2.0). It will delegate to real implementation.

In this case you need to chain with and.callFake() and pass the function you want to be called (can throw exception or whatever you want):

var someObject = jasmine.createSpyObj('someObject', [ 'method1', 'method2' ]);
someObject.method1.and.callFake(function() {
    throw 'an-exception';
});

And then you can verify:

expect(yourFncCallingMethod1).toThrow('an-exception');

Solution 2

If you are using Typescript, it's helpful to cast the method as Jasmine.Spy. In the above Answer (oddly I don't have rep for comment):

(someObject.method1 as Jasmine.Spy).and.callFake(function() {
  throw 'an-exception';
});

I don't know if I'm over-engineering, because I lack the knowledge...

For Typescript, I want:

  • Intellisense from the underlying type
  • The ability to mock just the methods used in a function

I've found this useful:

namespace Services {
    class LogService {
        info(message: string, ...optionalParams: any[]) {
            if (optionalParams && optionalParams.length > 0) {
                console.log(message, optionalParams);
                return;
            }

            console.log(message);
        }
    }
}

class ExampleSystemUnderTest {
    constructor(private log: Services.LogService) {
    }

    doIt() {
        this.log.info('done');
    }
}

// I export this in a common test file 
// with other utils that all tests import
const asSpy = f => <jasmine.Spy>f;

describe('SomeTest', () => {
    let log: Services.LogService;
    let sut: ExampleSystemUnderTest;

    // ARRANGE
    beforeEach(() => {
        log = jasmine.createSpyObj('log', ['info', 'error']);
        sut = new ExampleSystemUnderTest(log);
    });

    it('should do', () => {
        // ACT
        sut.doIt();

        // ASSERT
        expect(asSpy(log.error)).not.toHaveBeenCalled();
        expect(asSpy(log.info)).toHaveBeenCalledTimes(1);
        expect(asSpy(log.info).calls.allArgs()).toEqual([
            ['done']
        ]);
    });
});

Solution 3

Angular 9

Using jasmine.createSpyObj is ideal when testing a component where a simple service is injected. For example: let's say, in my HomeComponent I have a HomeService (injected). The only method in the HomeService is getAddress(). When creating the HomeComponent test suite, I can initialize the component and service as:

describe('Home Component', () => {
    let component: HomeComponent;
    let fixture: ComponentFixture<HomeComponent>;
    let element: DebugElement;
    let homeServiceSpy: any;
    let homeService: any;

    beforeEach(async(() => {
        homeServiceSpy = jasmine.createSpyObj('HomeService', ['getAddress']);

        TestBed.configureTestingModule({
           declarations: [HomeComponent],
           providers: [{ provide: HomeService, useValue: homeServiceSpy }]
        })
        .compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(HomeComponent);
            component = fixture.componentInstance;
            element = fixture.debugElement;
            homeService = TestBed.get(HomeService);
            fixture.detectChanges();
        });
    }));

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

    it("should display home address", () => { 
        homeService.getAddress.and.returnValue(of('1221 Hub Street'));
        fixture.detectChanges();

        const address = element.queryAll(By.css(".address"));

        expect(address[0].nativeNode.innerText).toEqual('1221 Hub Street');
    });
 });

This is a simple way to test your component using jasmine.createSpyObj. However, if your service has more methods more complex logic, I would recommend creating a mockService instead of createSpyObj. For example: providers: [{ provide: HomeService, useValue: MockHomeService }]

Hope this helps!

Share:
99,959

Related videos on Youtube

Adelin
Author by

Adelin

Experienced professional with a demonstrated history of working in the field of software engineering and informational technologies. Skilled in a variety of programming languages/technologies and frameworks. Expert in design, development, and maintenance of enterprise web applications. Strong entrepreneurship mindset, and problem-solving skills. Solving challenging problems and learning new technologies is my main driver and motivation factor.

Updated on June 13, 2020

Comments

  • Adelin
    Adelin about 4 years

    According to the Jasmine documentation, a mock can be created like this:

    jasmine.createSpyObj(someObject, ['method1', 'method2', ... ]);
    

    How do you stub one of these methods? For example, if you want to test what happens when a method throws an exception, how would you do that?

    • EricG
      EricG over 11 years
      You can try to chain it with andCallThrough. It isnt clearly documented :/
  • alxndr
    alxndr almost 10 years
    Jasmine 2.0 has changed the syntax to .and.callFake(), .and.callThrough(), .and.returnValue() jasmine.github.io/2.0/introduction.html#section-Spies
  • Peter Morris
    Peter Morris about 7 years
    The accepted answer doesn't compile for me (jasmine 2.5) but this solution worked!
  • Kai
    Kai about 7 years
    Mini-improvement - todoService: {[key: string]: jasmine.Spy} = jasmine.createSpyObj(...); todoService.anyMethod.and.... - no need to cast to Spy every time.
  • Eric Swanson
    Eric Swanson over 6 years
    Thanks @Kai, I added some detail. I needed (wanted?) type recognition as the primary type rather than as a dynamic Spy object. I personally wanted the object to act and feel like the real object and then only cast to Spy when I was testing.