0

I have a workshop-edit component that (in order):

  • Build the form
  • Retrieve the workshop to edit
  • Update form values with workshop values

Here is the code:

ngOnInit() {
  this.buildForms();
  this.initialize();
}

async initialize(): Promise<void> {
  const id = this.route.snapshot.params.id;

  this.workshop = await this.workshopService.find(id); // in real this is in a trycatch block
  this.updateFormValues();
}

buildForms(): void {
  this.form = ... // not important, this is not the problem
  this.discussesForm = this.formBuilder.group({
    array: this.formBuilder.array([], Validators.required),
  });
}

updateFormValues(): void {
  this.form.patchValue(this.workshop);
  this.workshop.ListDebates.forEach((discussion, index) => {
    this.addDiscussion();
    (this.discussesForm.get('array') as FormArray).at(index).patchValue({ // This line will throw error while UT.
      title: discussion.Title, description: discussion.Description, key: discussion.Key,
    });
  });
}

addDiscussion(): void {
  (this.discussesForm.get('array') as FormArray).push(this.formBuilder.group({
    title: [null],
    description: [null],
    key: [null],
  });
}

workshop.ListDebates look like:

[
  {
    Key: 1,
    Title: 'title',
    Description: 'description',
  },
]

So, all the code above works fine, but i'm trying to unit test the updateFormValues method.

This is what I tried:

it('should update form values', () => {
  spyOn(component, 'addDiscussion');
  component.workshop = { Title: 'fake title', ListDebates: [
    { Key: 1, Title: 'fake', Description: 'fake' },
    { Key: 2, Title: 'fake', Description: 'fake' },
  ]} as any as IColabEvent;
  component.updateFormValues();
  expect(component.form.value.Title).toEqual('fake title'); // test OK
  expect((component.discussesForm.get('array') as FormArray).controls.length).toEqual(2); // test KO, expected 0 to be 2
  expect((component.discussesForm.get('array') as FormArray).at(0).value).toEqual(...); // test KO (not runned)
});

Everytime I get error: Cannot read property 'patchValue' of undefined (in the updateFormValues method).

I've tried lots of things (and random things like adding fixture.detectChanges()) but I don't find a way to fix it.

What is weird is that addDiscussion is called 2 times, so I wonder why my FormArray control is undefined.

I've console.log() some things and it look like addDiscussion is called but isn't pushing a group like it must does.

I repeat myself but in the real app it's working as intended.

PierreD
  • 860
  • 1
  • 14
  • 30
  • In `updateFormValues` right before the first line, do `console.log(this.form)` and maybe `debugger` right after it. Run the test with `fit` and open the developer tools right away (pressing F12). The `debugger` should be tripped and you can see the value of `this.form` in the console. Judging by the error message, I bet `this.form` is undefined at that instance in time. Maybe `buildForms` is not being called before directly calling `updateFormValues` in your test. – AliF50 Jan 03 '20 at 12:30
  • `this.form` is different `this.discussesForm`. In my test the first expect pass so it means `buildForm` is called. I'm unable to open tests withing browser (only cli) so for debuging it's kinda hard tho. – PierreD Jan 03 '20 at 12:37
  • Sorry, I misinterpreted. – AliF50 Jan 03 '20 at 13:04
  • I would find a way to debug using the browser or some method of debugging so it makes tasks like this easier. But I think I know what is the issue. In your test you are spying on `addDiscussion`. This will just create a "watcher" for that function and not actually call its implementation. To have both a "watcher" and to call the function, you can do `spyOn(component, 'addDiscussion').and.callThrough()`. In fact, you can get rid of this `spyOn`, I don't think you need it. – AliF50 Jan 03 '20 at 13:08
  • Oh, i used spyOn for testing haveBeenCalledTimes(2) but i don't need it anymore now u're right. – PierreD Jan 03 '20 at 13:13
  • I think to set `component.workshop` like you did in your test is not a good practice. You should be mocking `workshopService` to return the desired data in order to set `workshop`. And since the `initialize` method is `async` and you are not `await`ing it. There may be a race condition that after you setting `workshop` manually it is set again by the service. – Eldar Jan 06 '20 at 15:17

1 Answers1

1

Rather than something being wrong with your test cases, it is in fact your code that has an issue. There is no need for you to use addDiscussion first to create an object with null values and then use patchValue to set the values. Instead, set the values as you create the form group itself. Change your addDiscussion function to accept the discussion parameter.

addDiscussion(discussion = {}): void {
    this.discussesForm.get('array').push(this.formBuilder.group({
        title: discussion.Title || null,
        description: discussion.Description || null,
        key: discussion.Key || null
    }));
}

Then in updateFormValues, in your foreach loop, get rid of the patchValue code and pass discussion instead.

this.workshop.ListDebates.forEach(discussion => {
    this.addDiscussion(discussion);
});

Apart from this, as already mentioned in the comments, the addDiscussion no longer needs to be spied upon since your test depends on it. Once this is done, your tests should be working.

nash11
  • 8,220
  • 3
  • 19
  • 55
  • Yes! Sometime I don't think that the problem come from my ts and not from my test... I just needed to add `addEmptyDiscussion()` because users may add empty line in the table ^^ Thanks. – PierreD Jan 07 '20 at 09:13
  • 1
    Anytime :) it's not necessary to create a new function to add empty discussion. Instead if you just call `addDiscussion` alone without any parameters, it would be sufficient. I updated the answer so that `discussion` defaults to an empty object if you don't pass any parameters to `addDiscussion` hence creating an empty line in the table with `null` values. – nash11 Jan 07 '20 at 11:44