Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with Decorator when testing component #91

Closed
f1ght4fun opened this issue May 8, 2020 · 13 comments
Closed

Issue with Decorator when testing component #91

f1ght4fun opened this issue May 8, 2020 · 13 comments

Comments

@f1ght4fun
Copy link

f1ght4fun commented May 8, 2020

I actually got same issue as the paradoxm in closed Issue #81

I have 1 unit test which doesnt raise this issue on the component which is decorated with UntilDestroy(). This component doesnt have any component level providers. Simple

And in another case it breaks with same error mentioned in the original ticket.
The only difference in two tests I noticed - it happens whenever you override components to inject component providers after TestBed.configureTestingModule({}) and before compileComponents().

In other words in case you have component decorated and you inject any providers component-level (services) it doesn't play nice

Originally posted by @f1ght4fun in #81 (comment)

@arturovt
Copy link
Collaborator

arturovt commented May 8, 2020

How do you expect us to help you if you don't provide any reproducible example? 🤔

🙂

@f1ght4fun
Copy link
Author

f1ght4fun commented May 8, 2020

Apologies. I cannot post real example so I was doing a mocking scenario to show you ;)

Working Component and Test:

@UntilDestroy()
@Component({
  selector: 'app-working-component',
  template: '<div>banana</div>',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class WorkingTestComponent implements OnInit {
  ngOnInit(): void {
    interval(1000)
      .pipe(
        untilDestroyed(this),
        take(10),
        tap(() => console.log('test'))
      )
      .subscribe();
  }
}
describe('WorkingTestComponent', () => {
  let component: WorkingTestComponent;
  let fixture: ComponentFixture<WorkingTestComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [WorkingTestComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(WorkingTestComponent);
    component = fixture.componentInstance;
  });

  afterEach(() => {
    fixture.destroy();
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
});

Not Working Component and Test (including Service as a provider for Component):

Service:

@UntilDestroy()
@Injectable()
export class TestService {
  interval$: Observable<any> = interval(1000);

  constructor() {
    this.interval$.pipe(untilDestroyed(this)).subscribe(() => {
      console.log('log from service');
    });
  }
}

Component:

@UntilDestroy()
@Component({
  selector: 'app-not-working-component',
  template: '<div>banana</div>',
  providers: [TestService],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class NotWorkingTestComponent implements OnInit {
  constructor(private testService: TestService) {}
  ngOnInit(): void {
    this.testService.interval$
      .pipe(
        untilDestroyed(this),
        take(10),
        tap(() => console.log('test'))
      )
      .subscribe();
  }
}

Test NOT WORKING:

@Injectable()
class MockService {
  interval$ = interval(3000);
}

describe('NotWorkingTestComponent', () => {
  let component: NotWorkingTestComponent;
  let fixture: ComponentFixture<NotWorkingTestComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [NotWorkingTestComponent]
    })
      .overrideComponent(NotWorkingTestComponent, {
        set: {
          providers: [{ provide: TestService, useClass: MockService }]
        }
      })
      .compileComponents();

    fixture = TestBed.createComponent(NotWorkingTestComponent);
    component = fixture.componentInstance;
  });

  afterEach(() => {
    fixture.destroy();
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
});

Running not working example it gives
Error: untilDestroyed operator cannot be used inside directives or components or providers that are not decorated with UntilDestroy decorator

@arturovt
Copy link
Collaborator

arturovt commented May 9, 2020

Sorry, I ain't able to reproduce it. This would be great if you'd have provided a reproducible example (git repo).

@f1ght4fun
Copy link
Author

f1ght4fun commented May 9, 2020

okay, that is totally weird. this is the actual code i have while running which gives me this error.

not sure what else i can do and how making a git repo going to help with that since its already not working for you

@arturovt
Copy link
Collaborator

arturovt commented May 9, 2020

Weird what? I don't have time to create the project manually, copy-paste that code and try to reproduce it locally. What I would like to see is a link to git repository and instructions:

  • yarn install
  • yarn ng test
  • See the error

This is normal policy. In most projects issues reported without a reproduction are closed immediately, e.g. if you open an issue in Angular's repository.

Since I give of my personal time to work on these issues and I would rather be spending my time fixing issues than chasing down the unreproducible issue. 😉

@f1ght4fun
Copy link
Author

f1ght4fun commented May 9, 2020

Hey,

I do understand it is hard for you to do it w/o reproducible scenario but I wouldn't continue previous ticket or open a new one if it was working =)

It also kinda took some time to make a scenario to reproduce and now the project to run it gracefully. Anyways I am attaching zip with solution ... just npm i and than ng test to see the issue (it has exactly versions of angular 9 libs as the project I got this error in)

TestExample.zip

@arturovt
Copy link
Collaborator

arturovt commented May 9, 2020

Thanks for reproduction. I was able to reproduce it, this is clearly a bug.

@f1ght4fun
Copy link
Author

As i was saying it happens only in case you override component with injections having decorator.

In case you unit test just service that is decorated, or just a component that is decorated but without providers - it works fine.

Tried to also debug this if it is of any help and it seems that it actually fails on the component rather than service.

Wild guess is that override component from test suite somehow removes decorator when you override - but needs more investigation

@arturovt
Copy link
Collaborator

Yes, overrideComponent overrides metadata and component definition, thus it loses everything that UntilDestroy decorator has applied:

image

@arturovt
Copy link
Collaborator

Honestly saying don't know what to propose here.

@NetanelBasal do you have any thoughts?

@NetanelBasal
Copy link
Member

NetanelBasal commented May 11, 2020

You can invoke it manually:

fdescribe('NotWorkingTestComponent', () => {
  let component: NotWorkingTestComponent;
  let fixture: ComponentFixture<NotWorkingTestComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [NotWorkingTestComponent],
    })
      .overrideComponent(NotWorkingTestComponent, {
        set: {
          providers: [{ provide: TestService, useClass: MockService }],
        },
      })
      .compileComponents();

    UntilDestroy()(NotWorkingTestComponent); <=======
    fixture = TestBed.createComponent(NotWorkingTestComponent);
    component = fixture.componentInstance;
  });

  afterEach(() => {
    fixture.destroy();
  });

  it('should create', () => {
    fixture.detectChanges();
    expect(component).toBeTruthy();
  });
});

@f1ght4fun
Copy link
Author

Thanks,

it is a workable solution to overcome this issue!

@chat-du-cheshire
Copy link

chat-du-cheshire commented Mar 5, 2021

Hi, got same issue with spectator. Fixed with UntilDestroy()(MyComponent) before createComponent() invoke.

// my.component.ts
@UntilDestroy()
@Component({...})
export class MyComponent implements OnInit {
  interval$ = interval(1000)

  ngOnInit() {
    this.interval$.pipe(untilDestroyed(this)).subscribe()
  }
}

// my.component.spec.ts
describe('MyComponent test', () => {
  let spectator: Spectator<MyComponent>;
  const createComponent = createComponentFactory(MyComponent);

  beforeEach(() => {
    UntilDestroy()(MyComponent); // <- Here

    spectator = createComponent();
  });

  it('should create', () => {
    expect(spectator.component).toBeDefined();
  });
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants