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

Feature Request: MockBuilder strict mode with standalone components #7800

Open
satanTime opened this issue Dec 25, 2023 Discussed in #7577 · 4 comments
Open

Feature Request: MockBuilder strict mode with standalone components #7800

satanTime opened this issue Dec 25, 2023 Discussed in #7577 · 4 comments

Comments

@satanTime
Copy link
Member

Discussed in #7577

Originally posted by StavNoyAkur8 November 22, 2023
Strict mode is a great feature.

It is activated when passing 2+ parameters - typically the tested component and it's module.
But standalone components don't have a module to pass as a second parameter.

We can simply pass null as the second parameters, or an empty array (which would usually be used for keeping multiple modules), but that's not obvious, and hard to enforce on a large project:

MockBuilder(TargetComponent, null);
MockBuilder(TargetComponent, []);

What I'd like: Detect that a component is standalone, make it activate strict mode.

Alternative: Allow global config of strict mode the default, regardless of standalone. Probably a bigger feature altogether

@marcelhohn
Copy link

What exactly would be the benefit of strict mode when using standalone components? I tried to imagine a use case where some import of the standalone component is missing, the production code fails and the test still passes. However, I couldn't come up with one.

I created this simple example, which tests a standalone component that imports a pipe and a module and does not use strict mode in the tests. At least one of the tests fails if I remove either one of the imports from the imports-array of the component decorator. If the tests would have passed despite the missing imports in the production code, I would have seen the point of using strict mode. However, this was not the case.

@Component({
  selector: 'app-test',
  standalone: true,
  imports: [AsyncPipe, ReactiveFormsModule],
  template: `
    <p>{{ message$ | async }}{{ name.value?.trim() || 'World' }}!</p>
    <input [formControl]="name" />
  `,
  styles: ``,
})
class TestComponent {
  protected message$: Observable<string> = of('Hello, ');
  protected name = new FormControl('');
}

describe('TestComponent', () => {
  beforeEach(async () =>
    MockBuilder(TestComponent).keep(AsyncPipe).keep(ReactiveFormsModule),
  );

  it('should create', () => {
    const fixture = MockRender(TestComponent);
    expect(fixture).toBeDefined();
  });

  it('should show default message', () => {
    const fixture = MockRender(TestComponent);
    expect(fixture.nativeElement.querySelector('p').textContent).toContain(
      'Hello, World!',
    );
  });

  it('should show personalized message', () => {
    const fixture = MockRender(TestComponent);
    const input = fixture.nativeElement.querySelector('input');

    input.value = 'John';
    input.dispatchEvent(new Event('input'));
    fixture.detectChanges();

    expect(fixture.nativeElement.querySelector('p').textContent).toContain(
      'Hello, John!',
    );
  });
});

It would be great if someone could clarify the purpose of strict mode when using standalone components!

@StavNoyAkur8
Copy link

StavNoyAkur8 commented Sep 16, 2024

The benefit is the same as in non-standalone: making sure the test code isn't accidentally providing anything not provided in real code.

@Injectable()
export class FooService {
}

@Component({
  selector: 'app-target',
  template: `hello`,
  standalone: true,
})
class TargetComponent {
  constructor(fooService: FooService) {
  }
}


describe('my sandbox', () => {
  it('should fail because service missing', async () => {
    await MockBuilder(TargetComponent);
    expect(() => MockRender(TargetComponent)).toThrow();
  });

  it('should fail to mock service because service is missing in implementation, in strict mode', async () => {
    // Notice passing a second argument `[]`, which activates strict mode
    await expectAsync(MockBuilder(TargetComponent, []).mock(FooService)).toBeRejectedWithError();
  });

  // This will currently fail
  it('should fail to mock service because service is missing in implementation, without explicit strict mode', async () => {
    await expectAsync(MockBuilder(TargetComponent).mock(FooService)).toBeRejectedWithError();
  });
});

Maybe FooService is supposed to be provided by a parent context, in which case it should be in the second argument, or use provide() instead of mock(). Maybe it was accidentally removed, or the code author simply forgot to add it to provide it.

@marcelhohn
Copy link

Thanks a lot for clarifying! Just to be clear, out of the three example tests you provided, the last one will fail, because MockBuilder does not throw an error in flex mode.

@StavNoyAkur8
Copy link

Thanks a lot for clarifying! Just to be clear, out of the three example tests you provided, the last one will fail, because MockBuilder does not throw an error in flex mode.

Yes, sorry, that's what I meant. I added that as a comment the example

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

No branches or pull requests

3 participants