1

I would like to test a function. To call that function in unit test I need to create an instance of the component class. The component class contains different services as arguments in the constructor including the service CookieService. The service depends on the third-library 'ngx-cookie' which has setters and getters.

I tried to mock the service to be able to create an instance of the component class like this:

import {CookieService} from "ngx-cookie-service";
....
    
 beforeEach(async(() => {
            const mockedFormBuilder = jasmine.createSpyObj('FormBuilder', ['control', 'array', 'group']);
            
            const get = function get(name: string): string {
            
                return 'test...';
            };

    TestBed.configureTestingModule({
                declarations: [HelloWorldComponent,...],
                imports: [
                    ...    
                ],
                providers: [
                    {provide: FormBuilder, useValue: mockedFormBuilder},
                    {provide: CookieService, useValue: get('name')},
                ]
    
            })
                .compileComponents();
    
        }));

describe('getArr', () => {

    it('should return array', () => {

        const text = 'text@domain.com';
        const textArr = ['text@domain.com']

        let getArr: string[];


        // @ts-ignore
        getArr = new HelloWorldComponent(FormBuilder,CookieService).getTextArray(tex);

        expect(getArr).toBe(textArr);
    })
})

Following error appears when running the test:

TypeError: this.cookieService.get is not a function
    at <Jasmine>
    at new HelloWorldComponent(http://localhost:9876/_karma_webpack_/src/app/helloworld.component.ts:63:51)
    at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/helloworld.component.spec.ts:113:28)
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:359:1)
    at ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:308:1)
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:358:1)
    at Zone.run (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:124:1)
    at runInTestZone (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:561:1)
    at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:576:1)

This is how the ComponentClass looks like:

export class HelloWorldComponent {
   constructor(private fb: FormBuilder, private cookieService:CookieService){
     const cookie:string=this.cookieService.get('somename')
   }
}

I understand that the cookieService has no function but a getter and setter, but I do not quite get how to mock the getter and how to correctly insert it in providers

Does someone came accross the same error when testing and how did you solved it?

Any help would be very appreciated!


Update: Use Stub Service

export class CookieServiceStub{
    cookie:string= 'gdhjgsfgsjdfg';
    get (name:string):string{
        return this.cookie;
    }
}

In Spec file: ...

TestBed.configureTestingModule({
                declarations: [HelloWorldComponent,...],
                imports: [
                    ...    
                ],
                providers: [
                    {provide: FormBuilder, useValue: mockedFormBuilder},
                    {provide: CookieService, useClass:CookieServiceStub},
                ]

            })
                .compileComponents();

        }));
Hiraja
  • 75
  • 1
  • 9

2 Answers2

2

You are currently providing the result of the call get('name') as the value of your CookieService service. I think this is incorrect... here:

{provide: CookieService, useValue: get('name')}

It is a little easier to create a Stub Class in this case:

first figure in your component the functions you are calling from the service, then create a Stub Class for it:

class CookieServiceStub{
  get(name: string) {
    return 'your value'
  }

  secondFunction() {} 
  // add as many as necessary
}

Then you can replace your provision of the CookieService with your stub class:

{provide: CookieService, useClass: CookieServiceStub}

Also in your test, you are creating a new instance of the component class, out of angular's scope, This needs to be adjusted. Instead of doing this:

getArr = new HelloWorldComponent(FormBuilder,CookieService).getTextArray(tex);

you should create it with the TestBed:

...  
  let component: HelloWorldComponent;
  beforeEach(() => {
    fixture = TestBed.createComponent(HelloWorldComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  describe('getArr', () => {
    it('should return array', () => {
        const text = 'text@domain.com';
        const textArr = ['text@domain.com']

        let getArr: string[];

        getArr = component.getTextArray(tex);

        expect(getArr.length).toBe(1);
        expect(getArr[0]).toBe(text);
    })
})
...
The Fabio
  • 5,369
  • 1
  • 25
  • 55
  • Ok thank I will try this. Can I ask you a general question, where do I create the Stub Class in the folder from the service or what is here the best practice? – Hiraja Jun 08 '22 at 11:31
  • I usually have a folder for class stubs, especially when stubbing imported libs. But I don't think there is a written best practice for this..."Keep it simple" is what I go for – The Fabio Jun 08 '22 at 11:36
  • So I tried this but still get the same error. Is it because for the service the third library ngx-cookie-service is used? – Hiraja Jun 08 '22 at 11:44
  • no... this approach should stub any angular service, i does not matter if it is coming from an imported lib or from your code. did you change the "useValue" for "useClass" ? – The Fabio Jun 08 '22 at 11:50
  • Thanks for the clarification. Yes I changed it to useClass. I can update my question – Hiraja Jun 08 '22 at 12:32
  • I have another question, the service is used as argument in the constructor of the component class, when creating an instance of this class to call any method, do I then use the CookieService? – Hiraja Jun 09 '22 at 07:47
  • angular's [magic](https://angular.io/guide/dependency-injection) gets the instantiated service from other classes where it is provided, or from the TestBed for specs. I see another problem now... I will update the answer – The Fabio Jun 09 '22 at 11:30
  • I as well have updated my question : https://stackoverflow.com/questions/72557856/failed-errors-in-unit-testing-angular and tried some different approach – Hiraja Jun 09 '22 at 11:50
  • I think it worked but the tests are not running through, due to the error: 'Failed: AnotherComponent is not part of any NgModule or the module has not been imported into your module.' I will however upvote your answer, can you please have a look on this question: https://stackoverflow.com/questions/72557856/failed-errors-in-unit-testing-angular – Hiraja Jun 09 '22 at 12:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245451/discussion-between-the-fabio-and-hiraja). – The Fabio Jun 09 '22 at 12:23
0

jasmine.createSpyObj is used to create mocks that will spy on methods. It will return an object for each property defined in the spy.

You can create a mockCookieService and add the get method

mockCookieService = jasmine.createSpyObj('CookieService', ['get']);

Try like this:

describe('Service: ', () => {

  let mockCookieService;

  beforeEach(() => {
    mockCookieService = jasmine.createSpyObj('CookieService', ['get']);

    TestBed.configureTestingModule({
      imports: [
          ...
      ],
      providers: [
        { provide: CookieService, useValue: mockCookieService },
      ]
    });

  });

You can refer this article

Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79