Repository: angular
Issue: Mocking Directive in standalone component test doesn't work
Labels: area: testing,cross-cutting: standalone

### Which @angular/* package(s) are the source of the bug?

core (TestBed)


### Description

For a project I'm working on, we started to migrate to Standalone components, but we ran into issues on various parts with overriding various dependencies. We have mocked a lot of dependencies to not run their internals.

As an example, for one component that shows a button to copy text to clipboard, we use the CDK CopyToClipboard directive. However, we ran into problems overriding the directive in our test, which was still working fine in a non-standalone situation.

On the stackblitz I've created 2 variants of the test, as we originally used @ngneat/spectator for our tests, but even with testbed I wasn't able to make it work. In the test I try to see if the console logs from the mocked component are being done but for now it doesn't take.

The code as of now:

```
import { Directive, Input } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { MockComponent } from 'ng-mocks';
import { CopyToClipboardComponent } from './copy-to-clipboard.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';

// simple mock directive to capture the input. We're not going to test the cdk logic of the copy
let clipboardResult = '';
@Directive({ selector: '[cdkCopyToClipboard]', standalone: true })
class MockCdkCopyToClipboard {
  // text to copy to clipboard
  constructor() {
    console.log('directive mocked'); // Problem: I'm not seeing this in the console
  }
  @Input() set cdkCopyToClipboard(value: string) {
    console.log('text copied', value); // Problem: I'm not seeing this in the console
    clipboardResult = value;
  }
}

describe('CopyToClipboardComponent - Testbed', () => {
  let component: CopyToClipboardComponent;
  let fixture: ComponentFixture<CopyToClipboardComponent>;
  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      imports: [
        MockComponent(MatIcon),
        MockComponent(MatIconButton),
        MockCdkCopyToClipboard,
      ],
    })
      /**
       * suggested by https://stackoverflow.com/a/75243037/3222860
       * but still doesn't show the console log from the mocked directive
       */
      .overrideComponent(CopyToClipboardComponent, {
        remove: { imports: [CdkCopyToClipboard] },
        add: { imports: [MockCdkCopyToClipboard] },
      })
      .createComponent(CopyToClipboardComponent);
    clipboardResult = '';
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should show the clipboard button when there is text to copy', () => {
    component.textToCopy = 'test';
    fixture.detectChanges();
    expect(
      fixture.nativeElement.querySelector('.clipboard-button')
    ).toBeTruthy();
    expect(clipboardResult).toEqual('test');
  });

  // a few more tests
});
```

And the component we try to test is as follows: 

```
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
import { Component, Input, OnChanges } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';

/**
 * Show clipboard to copy text to clipboard.
 * Example that I can't seem to fix when moving to standalone components.
 */
@Component({
  selector: 'app-copy-to-clipboard',
  template: `
  @if (showButton) {
    <button
      class="clipboard-button mat-icon-button-small"
      mat-icon-button
      type="button"
      [cdkCopyToClipboard]="textToCopy"
    >
      <mat-icon>content_copy</mat-icon>
    </button>
    }
    `,
  standalone: true,
  imports: [
    CdkCopyToClipboard, // I can't seem to override this dependency, its always using the real one
    MatIcon,
    MatIconButton,
  ],
})
export class CopyToClipboardComponent implements OnChanges {
  @Input() textToCopy!: string;

  showButton = false;

  ngOnChanges(): void {
    this.showButton = this.show();
    console.log('showButton', this.showButton); // this is logging just fine
  }

  show() {
    return (
      !!this.textToCopy &&
      this.textToCopy !== '-' &&
      this.textToCopy?.toLowerCase() !== 'null'
    );
  }
}
```

### Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-um7c3g?file=src%2Fcomponents%2Fcopy-to-clipboard.component.testbed.spec.ts


And this is the directive I'm trying to mock:
https://github.com/angular/components/blob/main/src/cdk/clipboard/copy-to-clipboard.ts (which is also exported as a module but I don't think we use that right now with standalone. We did use it pre-migration)

### Please provide the exception or error you saw

```true
It doesn't log anything from the mocked directive. I would expect it to log the items in the constructor and the input to indicate that it is being used by the tests
```


### Please provide the environment you discovered this bug in (run `ng version`)

```true
Angular 18.2.3
Karma 6.4.4
Jasmine 5.2.0
@ngneat Spectator 19 (wrapper around testbed with more functionality and performance improvements)
```


### Anything else?

I found it difficult to get the proper documentation for overriding as the current documentation focuses on providers rather than mocked components and directives. I often see suggestions to just use ngmocks and while it is valid, a simple mocked component or directive still makes it clearer what I want to test.

We also tried to move to Jest a few times but that also didn't work entirely (which might be related to this issue, I don't know), but we stuck with Karma/Jasmine for now. There's about 1000 tests in our project of which 73 don't work because of similar reasons to this one. It prevents us from moving to standalone components, which seems to be a bigger thing now that it has become the default in angular 19 (alpha).