angularunit-testingjasmineangular-directive

Angular10 attribute directive mock is not working. Trying to get element in template: cannot read properties of null (reading 'nativeElement')


I'm trying to mock a Directive in my jasmine unit test using Angular 10. The directive evaluates a condition based on input and will either render or not render the template base on the condition. Using the mock directive, I'm expecting the "annoying directive" to be ignored, and to have the ability to grab native elements. However, it does not appear to be mocking it. If I remove the the div containing the annoyingDirective from the template and replace with the commented out div, everything works. The directive uses a store but I don't see why I would need to mock that. Thanks!

html template of Component to test

   <div *annoyingDirective="input_data1; input_data2: true" class="class1">
      <!--<div class="class1">-->
      <div class="class2" >I'm trying to get this element
         <mat-slide-toggle
          [checked]="isChecked"
          class="test-toggle"
          (change)="toggleSlide($event)"
        >
         </mat-slide-toggle>
     </div>
    </div>

Unit test

    @Directive({
      selector: '[annoyingDirective]'
    })
    export class MockAnnoyingDirective {
      @Input() input_data1: string[];
      @Input('input_data2') input_data2: boolean = false;
    }

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

      beforeEach(async () => {
        TestBed.configureTestingModule({
          imports: [MatSlideToggleModule, TranslateModule.forRoot()],
          declarations: [ComponentToTest, MockAnnoyingDirective],
          providers: [..add providers]
          ]
        }).compileComponents();
      });
      beforeEach(() => {
        fixture = TestBed.createComponent(ComponentToTest);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });

      it('Should create component', () => {
        expect(component).toBeTruthy(); //no errors
       });

      describe('slide-toggle-selection', () => {
        it('should show the div', () => {
          const forTest = fixture.debugElement.query(By.css(".class2")).nativeElement;
          expect(forTest).toBeTruthy(); //TypeError: Cannot read properties of null 
      });
    });

annoying directive

    @Directive({
       selector: '[annoyingDirective]'
     })

    export class annoyingDirective implements OnInit, OnDestroy {
       @Input() input_data1: string[];
       @Input('input_data2') strictAccess: boolean = false;

       //it uses a store - do I need to mock this??
       @Select(UserState.permissions) permissions$: Observable<any[]>;

      constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<any>) 
        {}
      ngOnInit(){
         if(some condition based on permissions is met)
           return this.viewContainer.createEmbeddedView(this.templateRef);
       }}

Solution

  • Can you try changing your MockAnnoyingDirective to the following?

    @Directive({
          selector: '[annoyingDirective]'
        })
        export class MockAnnoyingDirective implements OnInit {
          @Input() input_data1: string[];
          @Input('input_data2') input_data2: boolean = false;
          
          constructor(
            private readonly templateRef: TemplateRef<any>,
            private readonly viewContainer: ViewContainerRef
          ) {}
    
          ngOnInit(): void {
            this.viewContainer.createEmbeddedView(this.templateRef);
          }
        }
    

    Since AnnoyingDirective is a structural directive (starts with *), in the mock, we have to paint what the directive is attached (the ngOnInit in the MockAnnoyingDirective) to or else we will have no div.