Angular2 unit testing : testing a component's constructor
Asked Answered
A

3

20

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);
  });
}
Aver answered 19/4, 2017 at 14:5 Comment(5)
Inject a fake DragulaService using the test bed and spy on its methods?Misapprehension
I would like to, the problem is that I can't reach the contrsuctor to test if my stub is calledAver
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).Misapprehension
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 methodAver
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.Misapprehension
M
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).

Misapprehension answered 19/4, 2017 at 14:23 Comment(10)
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)Aver
@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.Misapprehension
The point is that I would like to know if the service is called in the constructor, not if the service is called when I call it. I mean I shouldn't have to call it, I would rather test if the constructor calls it correctly (so if someone changes the constructor code, it throws an error)Aver
@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.Misapprehension
@trichetriche why don't you try it out, and see what happens? Comment out one of the subscriptions and see if the relevant test fails. A test that you never see fail isn't much use, as you don't know if it's going to give you any information when it does.Misapprehension
Ok I think I get it. I already tried it and it works (minus some changes of course), I just thought that I needed to test if the constructor is called on the component creation and subscribed correctly. But instead, if I understood, I should test if when a drag event occurs, my code is triggered, because if it doesn't, it means that the subscrive didn't occur, is that it ?Aver
@triche yes, exactly! It's the DI system's job to instantiate the component and the interpreter's job to call the constructor when it's instantiated, you don't generally want to be unit testing the framework or language itself.Misapprehension
Ok I see, this will help me a lot in the next tests, thank you !Aver
@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.htmlMisapprehension
Thanks a ton for this answer!Chaplin
H
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.

Holily answered 20/8, 2017 at 18:59 Comment(1)
thnx it helped a lot.Leafy
P
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.

Pockmark answered 27/12, 2017 at 8:37 Comment(12)
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.Aver
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/…Pockmark
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 :DAver
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.Pockmark
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 !Aver
I am not telling you that at all sir.Pockmark
You can do this for every componentAver
14k lines of code for 200+ tests, my testing time was 6s is this real? What is your coverage? What is your secret?Pockmark
75% (required by the project), and a lot of tests were "factored" (several methods by tested at once because if their similar output), 6 to 7 seconds :)Aver
Do you realize that your question starts Angular2 unit testing: ? My answer is the only correct answer to your question given so far. This is actually how you unit test a constructor. No offense.Pockmark
Okay I see you're upset and I don't need drama or "I am right you are wrong" games. I'll let you have fun here if you want to :) my answer got here 8 months ago, you dug up a solved topic. You have your way of doing this, someone gave mine, whatever floats tour boat. So have fun, and bye !Aver
I came here looking for a solution to my problem. I was disappointed. I solved it and shared it. I was offended but I could not care less now. Whatever makes you happy.Pockmark

© 2022 - 2024 — McMap. All rights reserved.