question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dependency injection with @Self() is broken in tests

See original GitHub issue

🐞 bug report

Affected Package

@angular/core

Is this a regression?

Unknown

Description

The @Self() decorator does not work in tests to inject dependencies from directives placed on a component by a test-specific wrapper component.

E.g. if a I have a custom form control that injects NgControl using the @Self() decorator, as recommended by the Angular team:

constructor(@Self() ngControl: NgControl) {
  ngControl.valueAccessor = this;
}

And I try to use this in a test wrapper component:

@Component({
  template: `
    <form>
      <app-hello [formControl]="ctrl" name="test"></app-hello>
   </form>
 `
})
class TestWrapper {
  ctrl = new FormControl(''); 
}

describe('HelloComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [FormsModule, ReactiveFormsModule],
      declarations: [
        HelloComponent,
        TestWrapperComponent
      ],
    }).compileComponents();
  }));
});

Then test will fail due to an inability to inject NgControl.

🔬 Minimal Reproduction

https://github.com/jonrimmer/di-is-broken

If you run the app, you will see that it works fine.

If you run the test, you will see that it fails due to an inability to inject NgControl into the custom form control.

🔥 Exception or Error


Chrome 75.0.3770 (Mac OS X 10.14.5) HelloComponent should create FAILED
	No provider for NgControl ("[ERROR ->]<app-hello></app-hello>"): @0:0
	error properties: Object({ ngSyntaxError: true, ngParseErrors: [ No provider for NgControl ("[ERROR ->]<app-hello></app-hello>"): @0:0 ] })

🌍 Your Environment

Angular Version:





Angular CLI: 8.0.6
Node: 12.4.0
OS: darwin x64
Angular: 8.0.3
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.800.6
@angular-devkit/build-angular     0.800.6
@angular-devkit/build-optimizer   0.800.6
@angular-devkit/build-webpack     0.800.6
@angular-devkit/core              8.0.6
@angular-devkit/schematics        8.0.6
@angular/cli                      8.0.6
@ngtools/webpack                  8.0.6
@schematics/angular               8.0.6
@schematics/update                0.800.6
rxjs                              6.4.0
typescript                        3.4.5
webpack                           4.30.0

Anything else relevant?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:12 (4 by maintainers)

github_iconTop GitHub Comments

10reactions
jonrimmercommented, Aug 23, 2019

@mhevery I cannot, because the injection problem is happening in tests, as indicated by the issue title and description, and Stackblitz has no capability to run Angular unit tests… The linked repo is a ng new project with a single, simple commit applied. The bug text also already includes the description of what I am injecting and what is going wrong.

I spent considerable effort and time creating this simplified example. I find your suggestion that I didn’t, and that I’m asking you to perform unreasonable reverse engineering rude and unfounded. If your team doesn’t have the capacity to handle bug reports from the community, then you shouldn’t accept them, instead of insulting community members who make good-faith attempts to contribute.

5reactions
ShadowManucommented, Apr 7, 2020

In case anybody finds this useful, I’m able to fix this using TestbedStatic.overrideComponent:

  beforeEach(async () => {
    const NG_CONTROL_PROVIDER = {
      provide: NgControl,
      useClass: class extends NgControl {
        control = new FormControl();
        // tslint:disable-next-line: no-empty
        viewToModelUpdate() {}
      },
    };

    await TestBed.configureTestingModule({
      declarations: [SelectAmountComponent],
      imports: [FormsModule],
    })
      .overrideComponent(SelectAmountComponent, {
        add: { providers: [NG_CONTROL_PROVIDER] },
      })
      .compileComponents();
  });
Read more comments on GitHub >

github_iconTop Results From Across the Web

Dependency Injection Bad Practices | by Luís Soares - Medium
Solution: Dependencies should be created and injected in a single place/file, usually the app's entry point, i.e, “main” (i.e. the composition root). The...
Read more >
Unit tests break when I add new dependency to controller
I'm doing all my dependency injection using constructor-based injection. Thus far it's going well, but I've found myself doing something ...
Read more >
Use Dependency Injection to Make Your Code Testable
Dependency injection is a pretty straightforward yet incredibly powerful technique. In short, rather than a function having its dependencies ...
Read more >
Favor real dependencies for unit testing - Stack Overflow Blog
Only the imperative shell needs dependency injection to support unit testing. Which dependencies are required?
Read more >
Make services unit testable using dependency injection
This means that at the time of testing, you need to isolate your class from other classes it might call (a.k.a. its dependencies)....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found