21

All is in the title : how can one test what is done in the component's constructor ?

For your information, I am using a service that requires a setting, and I would like to see if the 2 methods that I call in the constructor are called correctly.

My component's constructor :

constructor(
  public router: Router,
  private profilService: ProfileService,
  private dragula: DragulaService,
  private alerter: AlertService
) {
  dragula.drag.subscribe((value) => {
    this.onDrag(value);
  });
  dragula.dragend.subscribe((value) => {
    this.onDragend(value);
  });
}
5
  • Inject a fake DragulaService using the test bed and spy on its methods? Commented Apr 19, 2017 at 14:10
  • 1
    I would like to, the problem is that I can't reach the contrsuctor to test if my stub is called Commented Apr 19, 2017 at 14:14
  • What do you mean "reach the constructor"? It gets called when the component is instantiated by the DI system in the test bed, when you TestBed.createComponent(YourComponent). Commented Apr 19, 2017 at 14:17
  • I mean, I want to test if the code written in the constructor is triggered, by testing the constructor as if it was any other method Commented Apr 20, 2017 at 7:28
  • You can invoke the constructor by manually creating a new YourComponent(...), but you should let the DI system do its job. That's how the unit will be used in practice, think about its public interface as the other components see it. As below, it sounds like you're writing (or trying to write) brittle tests that are too closely tied to current implementation. Commented Apr 20, 2017 at 7:50

3 Answers 3

22

I would inject a fake service using the DI system, which would mean writing the tests something like this:

describe('your component', () => {
  let fixture: ComponentFixture<YourComponent>;
  let fakeService;
  let dragSubject = new ReplaySubject(1);
  ...

  beforeEach(async(() => {
    fakeService = { 
      drag: dragSubject.asObservable(),
      ... 
    };

    TestBed.configureTestingModule({
      declarations: [YourComponent, ...],
      providers: [
        { provide: DragulaService, useValue: fakeService }, 
        ...
      ],
    });
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(YourComponent);
    fixture.detectChanges();
  });

  it('should do something when a drag event occurs', () => {
    dragSubject.next({ ... });
    fixture.detectChanges();
    ...
  });
});

This allows you to trigger "drag events" whenever you like by calling .next on the subject, which causes subscribers to the fields on the fake service to get called. You can then make assertions on the outcomes you expect from that.

Note that you do not need to call constructor yourself; this method is invoked when the DI system instantiates your component, i.e. when TestBed.createComponent is called.

I would recommend that you don't spy on the component methods (e.g. this.onDrag) and just make sure that they get called, but rather test that whatever those methods should do as a result happens; this makes the tests more robust to changes in the specific implementation (I wrote a bit about this on my blog: http://blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html).

Sign up to request clarification or add additional context in comments.

10 Comments

So you're telling me that instead of testing the constructor, I should call the wanted methods and see if they're called ? (I'm new to unit testing, I thought you had to test every method, including the constructors)
@trichetriche no, I'm saying that you test what the constructor does by ensuring that the events on the service are hooked up to the correct behaviour. You could simply test that the constructor calls .subscribe on the appropriate spied fields on a fake service (for example doing something like { drag: jasmine.createSpyObj('fake drag event', ['subscribe']), ... }), but that's very closely tied to the current specific implementation rather than the actual functionality you're implementing.
@trichetriche the service isn't called in the constructor, just subscribed to. The actual behaviour you're trying to implement, and therefore what you should be testing, is whatever happens when a drag event occurs. That's what the above test checks. "I mean I shouldn't have to call it" - call what? "so if someone changes the constructor code, it throws an error" - if the constructor changes such that the service is no longer subscribed to, and nothing else is changed to make sure the functionality still works, the test will fail. That's what's supposed to happen.
@trichetriche no problem. I actually wrote a blog post recently on our Angular testing for some colleagues, you may find it useful: blog.jonrshar.pe/2017/Apr/16/async-angular-tests.html
Thanks a ton for this answer!
|
17

The simple way to test anything inside constructor function is to create component instance and then test it.

it('should call initializer function in constructor', () => {
  TestBed.createComponent(HomeComponent); // this is the trigger of constructor method
 expect(sideNavService.initialize).toHaveBeenCalled(); // sample jasmine spy based test case
});

One thing to note that, if you want to distinguish between constructor and ngOnInit, then don't call fixture.detectChanges() inside beforeEach(). instead call manually whenever you need.

1 Comment

thnx it helped a lot.
0

Since OP states "I would like to see if the 2 methods that I call in the constructor are called correctly." I have a better approach.

Write a unit test. You don't need to use the test bed for this. It will slow down your tests a lot. Instantiate your mocks manually. Set your spies on the methods you're interested in and then call the component constructor manually with the stubs you've instantiated and set spies on. Then test if spied methods have been called correctly.

The key is to extend your stubs from the original service classes. jasmine.createSpyObj helps for mocking angular classes like Router.

12 Comments

There is already an accepted answer and it suits me particularly well. Your approach doesn't because it is a question for every component, every service I have ever made that has some code into the constructor.
You can do this for every component. You really shan't use the test bed for pure unit tests. Otherwise your tests take ages to complete. Trust me I am struggling with it right now. Check this out: chariotsolutions.com/blog/post/…
No offense, but it's kind of dumb to avoid using abstractions available while it gives so much features. For instance, when I test my services, mocking the Backend is very useful ! And it needs the testbed for that. And on a 14k lines of code for 200+ tests, my testing time was 6s, I think I'm good :D
I am offended. I am not telling you not to mock dude. I am telling you how you can mock it cleaner and faster. You should of course use the test bed when necessary. I am only saying that it is not needed for what you're asking. Thanks for the down vote.
You're telling me to test all my components like this. My original question was just how to test a constructor, not how to make all my tests. I almost always need the testbed, and when I don't, then I don't use it. But I won't make a rule out of that just because I'll gain 5ms. And I'm telling you that your answer is late, out of topic, and not suited for my cases. So yes, I down vote, and if you didn't know, I lose points too by doing so, so it's not out of pleasure !
|