Search code examples
angularjasminekarma-jasmineangular-test

Angular Unit Testing - stub complex object


In my unit tests, I have to create stubs that have a large number of properties.

So I would have an interface like this:

interface Person {
  id: string;
  name: string;
  age: number
  ...
}

Then I will have to create an object that implements this interface and pass some values to each property. Doesn't Jasmine have a function that receives an interface and returns an object with default values?

In the example above, I would get:

const stub: Person = createStubObj<Person>();
stub.id // ""
stub.name // ""
stub.getAge // 0

I did some research but didn't found anything useful.

createSpyObj doesn't seem to help me because I don't have any functions that I want to spy on. The object only has properties.

This case can happen when an HTTP request returns a complex object (many properties, nested properties). I'm wondering how can I mock that object in that case, without needing to specify a value for each property.

Something similar to what this library is doing: https://www.npmjs.com/package/ts-auto-mock#usage

I'm wondering if this can be achieved in Jasmine as well or if there is a better approach


Solution

  • I tend to create specific mock objects that can be injected into the component under test using the TestBed provider registration:

        TestBed.configureTestingModule({
            providers: [
                TestComponent,
                { provide: RealInterfaceType, useValue: new MockObjectType() }
            ]
        });
    

    You can then define the appropriate property values in the mock object. Equally you can use the mock object to be the response from a dependent service by setting up a spy:

        serviceSpy = jasmine.createSpyObj('SomeService', ['someFunction']);
        serviceSpy.someFunction.and.returnValue(new MockObjectType());
    

    For http calls, I generally test them as follows...

    let injector: TestBed;
    let service: TestService;
    let httpMock: HttpTestingController;
    const expectedResponse: TestObject = new TestObject();
    
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [
                TestService
        });
        injector = getTestBed();
        service = injector.get(TestService);
        httpMock = injector.get(HttpTestingController);
    });
    
    afterEach(() => {
        httpMock.verify();
    });
    
    describe('someFunction', () => {
        it('should return an Observable<UserAuthResponse>', () => {
            service.someFunction().subscribe(response => {
                expect(response).toBe(expectedResponse);
            });
            const req = httpMock.expectOne('http://the-expected-url');
            expect(req.request.method).toBe('GET');
    
            // return the static expected value
            req.flush(expectedResponse);
        });
    });
    

    This is merely testing the expecting http method and URL though.