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

Bug: inject function can not be mocked #9397

Open
wellwind opened this issue Jul 9, 2024 · 3 comments
Open

Bug: inject function can not be mocked #9397

wellwind opened this issue Jul 9, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@wellwind
Copy link

wellwind commented Jul 9, 2024

Description of the bug

The auto spy feature can not mock instance injected by inject function.

An example of the bug

service that want be mocked:

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class TodoListService {
  someMethod() {}
}

the component:

import { Component, inject } from '@angular/core';
import { TodoListService } from './todo-list.service';

@Component({
  selector: 'app-root',
  standalone: true,
  template: ``,
})
export class AppComponent {
  // can not be auto spied
  public todoListService = inject(TodoListService);

  // can be auto spied
  // constructor(private todoListService: TodoListService) {}

  someMethod() {
    this.todoListService.someMethod();
  }
}

test code:

import { TestBed } from '@angular/core/testing';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
import { AppComponent } from './app.component';
import { TodoListService } from './todo-list.service';

ngMocks.autoSpy('jasmine');

describe('AppComponent', () => {
  let component: AppComponent;

  beforeEach(() => MockBuilder(AppComponent));

  beforeEach(() => {
    component = MockRender(AppComponent).componentInstance;
  });

  it('should call call todoListService.someMethod', () => {
    const todoListService = TestBed.inject(TodoListService);

    component.someMethod();
    expect(todoListService.someMethod).toHaveBeenCalled();
  });
});

Link:

https://github.com/wellwind/ng-mocks-inject-demo

Expected vs actual behavior

In component, when I use ctor to inject service, it works fine.

Bue when I use inject function, I got errors:

   Error: <toHaveBeenCalled> : Expected a spy, but got Function.
   Usage: expect(<spyObj>).toHaveBeenCalled()

However, it should also passed the test.

@wellwind wellwind added the bug Something isn't working label Jul 9, 2024
@ste2425
Copy link

ste2425 commented Sep 6, 2024

If it helps, manually providing with .provide() chained after the MockBuilder does work.

Well it works in that i can mock the service but cannot use MockInstance to change that mock.

Just FYI incase it helps investigate.

@pcbowers
Copy link

pcbowers commented Oct 16, 2024

This is probably broader than auto spy (since spying doesn't necessarily have anything to do with mocking). Regardless, this behavior may be intentional. See #4973 (comment). Furthermore, you can see there's even a test ensuring the service is not mocked: https://github.com/help-me-mom/ng-mocks/blob/master/tests/issue-4282/test.spec.ts#L51-L61. This is related to issue #4282 where inject support was added. I did a little manual test and replaced readonly service = inject(TargetService); with constructor(readonly service: TargetService) {} in that very test, and confirmed that the test fails with constructor injection since the mock now exists.

@satanTime Is this intended behavior currently? If so, it may be better to label this as a feat instead of a bug. Regardless, it would be nice if functional dependency injection via inject worked similarly to how constructor dependency injection works where dependencies could be inferred and mocked automatically. Is there a workaround that would allow this in its current state? If not, it may be worth adding something about this in the docs. I was running ng generate @angular/core:inject-migration which broke all my tests, and it took me a while to stumble on this conclusion (and I don't really want to have to change all my tests to explicitly opt into those mocks if I don't have to).

@BojanKogoj
Copy link

Changed tests to use MockBuilder, but this is breaking some of them (all using NgbModal). In my case I don't even use inject function in the component.

@Component(...)
export class MyComponent {
  constructor(private modal: NgbModal) {}

and test

  it('...', () => {
    const ngbModal= TestBed.inject(NgbModal);
    jest.spyOn(ngbModal, 'open');
    ...
    expect(ngbModal.open).toHaveBeenCalled();
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants