Is it possible to mock an Attribute Directive in Angular?

12,749

Solution 1

The MockDirective does work, but it was misconfigured.

export function MockDirective(options: Component): Directive {
    const metadata: Directive = {
        selector: options.selector,
        inputs: options.inputs,
        outputs: options.outputs
    };
    return Directive(metadata)(class _ { });
}

Turned out to be a missing mock declaration for the @Input('skinColorPicker') property:

TestBed.configureTestingModule({
  declarations: [
      PrintSidebarComponent,
      MockDirective({ 
        selector: '[skinColorPicker]', 
        inputs: ['skinColorPicker'] }) // does work
  ]
})

Solution 2

My solution is identical to @jenson-button-event's solution, with some minor changes so it compiles in TypeScript.

export function MockDirective(options: Component): Directive {
  const metadata: Directive = {
    selector: options.selector,
    inputs: options.inputs,
    outputs: options.outputs
  };
  return <any>Directive(metadata)(class _ {}); // <----- add <any>
}

TestBed.configureTestingModule({
  declarations: [
      PrintSidebarComponent,
      MockDirective({ 
        selector: '[skinColorPicker]', 
        inputs: []  // <--- empty, unless the directive has inputs
      })
  ]
})

Solution 3

Since Component is Directive without template, we could directly use MockComponent. I guess the 'MockDirective' is also inspired by it.

Way of using MockComponent to mock directive is as follows:

MockComponent({
  selector: '[skinColorPicker]',
  inputs: ['skinColorPicker']
}),

Solution 4

This solution is exactly the same as @jenson-button-event's and @maia's solution, except for improved Typescript correctness. Without the improvements it did not work for me.

export function MockDirective(options: Component): Type<Directive> {
  const metadata: Directive = {
    selector: options.selector,
    inputs: options.inputs,
    outputs: options.outputs,
  };
  return Directive(metadata)(class MockDirectiveClass {
  });
}

TestBed.configureTestingModule({
  declarations: [
      PrintSidebarComponent,
      MockDirective({ 
        selector: '[skinColorPicker]', 
        inputs: []  // <--- empty, unless the directive has inputs
      })
  ]
})
Share:
12,749

Related videos on Youtube

jenson-button-event
Author by

jenson-button-event

Hacking around since 1984.

Updated on June 06, 2022

Comments

  • jenson-button-event
    jenson-button-event about 2 years

    I have the following directive that is applied to input tags. When running a jasmine spec on the host component I want it to ignore (mock) this directive since it has a dependency on jquery that I am not interested in testing.

    I have tried to create a MockDirective class but have not been successful. Anyone know how achieve this?

    @Directive({
        selector: '[skinColorPicker]'
    })
    export class ColorPickerDirective implements OnInit {
    
        @Input('skinColorPicker') initialColor;
    
        @Output() colorSelected: EventEmitter<string> = new EventEmitter<string>();
    
        constructor(private el: ElementRef) {}
    
        ngOnInit() {
    
           // legacy jQuery blah blah
        }
    }
    

    inside the host:

    <input skinColorPicker="'#555'" (colorSelected)="onPageBackgroundColorSelected($event)" 
     />
    

    the spec:

    describe('PrintSidebarComponent', () => {
      let component: PrintSidebarComponent;
      let fixture: ComponentFixture<PrintSidebarComponent>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
              PrintSidebarComponent,
              MockDirective({ selector: '[skinColorPicker]' }) // does not work
          ]
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(PrintSidebarComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });
    
    export function MockDirective(options: Component): Component {
        let metadata: Directive = {
            selector: options.selector,
            inputs: options.inputs,
            outputs: options.outputs
        };
        return Directive(metadata)(class _ { });
    }
    

    Can't bind to 'skinColorPicker' since it isn't a known property of 'input'.

    I saw this overrideDirective method but have not been able to find a decent example of it.

    One Solution Turned out to be a missing mock declaration for the @Input('skinColorPicker') property:

    MockDirective({selector: '[skinColorPicker]', inputs: ['skinColorPicker']})
    

    I still think seeing an example with the built in Testbed.overrideDirective function would be better.

    Plunkr

    • yurzui
      yurzui about 7 years
      Why should you mock it? Is it not working without mocking? Angular will not complain on attribute directive if you don't declare it in declarations array. Anyway there is NO_ERROR_SCHEMA for this purpose
    • jenson-button-event
      jenson-button-event about 7 years
      @yurzui i am mocking it mainly because it is not the system under test, but also because it has a legacy dependency on jQuery that i am not interested into bringing into my test suite.
    • jenson-button-event
      jenson-button-event about 7 years
      the error is still evident without the declaration, schemas: [ NO_ERRORS_SCHEMA ] is needed to prevent it, but that feels like sweeping it under the carpet (try ... catch.. continue.. crap)
    • yurzui
      yurzui about 7 years
    • jenson-button-event
      jenson-button-event about 7 years
      short answer: no. but it helped me solve it. i had 2 pickers in the html one with a default value, so had to add the input to the mock: MockDirective({selector: '[skinColorPicker]', inputs: ['skinColorPicker']}). thank you.
    • yurzui
      yurzui about 7 years
      skinColorPicker="#555" is key point in your question. In this case angular will complain if you don't declare @Input for your directive
  • Sebastian Patten
    Sebastian Patten about 6 years
    Don't think this compiles in modern TypeScript. Is there any less voodoo way of creating a mock directive?
  • maia
    maia about 6 years
    @SebastianPatten see my answer below
  • Aico Klein Ovink
    Aico Klein Ovink over 5 years
    I wonder, with this approach, is it possible to test the input values of the directive?