Angular单元测试报错:spyOn找不到login()对象,咨询TestBed问题
<spyOn>: could not find an object to spy upon for login() Error First, let's confirm your understanding of TestBed: You're mostly right! Angular's TestBed acts as a testing module builder that sets up a mini Angular environment tailored for your tests. It handles creating component instances, injecting dependencies (like services), configuring necessary imports (such as FormsModule or RouterTestingModule), and initializing component views. However, a key nuance is that it doesn't automatically detect all dependencies—you have to explicitly tell it which components, services, and modules your test needs via configureTestingModule.
Now, let's tackle that <spyOn> error. This issue almost always stems from one of three common mistakes:
Common Causes & Fixes
1. The service isn't properly provided in your TestBed configuration
If your LoginComponent depends on an AuthService (or similar) with the login() method, you need to include this service in the providers array of your TestBed setup. Without this, TestBed can't inject the service into your component, so there's no instance to spy on.
2. You're spying on the service class instead of an injected instance
spyOn requires an existing object instance to work. Trying to spy on the service class directly (e.g., spyOn(AuthService, 'login')) will fail because you're targeting the class, not an instantiated object. You need to get the injected service instance first.
3. The login() method isn't a public instance method
If login() is a private method or a static method on the service, spyOn won't be able to target it. For unit testing, service methods you want to mock/spy on should be public instance methods.
Example Correct Setup
Here's a complete example of how to configure TestBed and properly spy on the login() method:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LoginComponent } from './login.component'; import { AuthService } from '../services/auth.service'; import { FormsModule } from '@angular/forms'; // Include if using template-driven forms describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture<LoginComponent>; let authService: AuthService; // Holds the injected service instance beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [LoginComponent], // Declare your component imports: [FormsModule], // Add any modules your component uses providers: [AuthService] // Provide the service the component depends on }).compileComponents(); // Create component fixture and instance fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; // Get the injected service instance from TestBed authService = TestBed.inject(AuthService); // Trigger initial change detection fixture.detectChanges(); }); it('should call AuthService.login() when form is submitted', () => { // Spy on the login method of the injected service instance const loginSpy = spyOn(authService, 'login').and.returnValue(Promise.resolve(true)); // Simulate user input component.email = 'test@example.com'; component.password = 'test123'; // Trigger the submit method component.onSubmit(); // Verify the spy was called with the correct arguments expect(loginSpy).toHaveBeenCalledWith('test@example.com', 'test123'); }); });
Bonus: Using a Mock Service (Recommended)
For better isolation (so your component tests don't depend on the real service's implementation), you can provide a mock service instead:
// Create a mock AuthService const mockAuthService = { login: jasmine.createSpy('login').and.returnValue(Promise.resolve(true)) }; // In TestBed configuration: providers: [{ provide: AuthService, useValue: mockAuthService }] // Then in your test: expect(mockAuthService.login).toHaveBeenCalledWith(...);
This approach keeps your component tests focused on the component's behavior, not the service's logic.
内容的提问来源于stack exchange,提问作者a p

