You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular登录组件单元测试报错:NullInjectorError及subscribe未定义

Hey there, let's break down and fix those issues you're facing with your Angular LoginComponent tests.

1. Fixing the NullInjectorError: No provider for LocationStrategy

The root cause here is that you're providing a custom Router spy alongside RouterTestingModule, which creates a conflict. RouterTestingModule already sets up all necessary routing dependencies (including LocationStrategy) for testing, so adding your own Router provider overrides that setup and breaks the dependency chain.

Solution:

Remove the custom Router provider from your TestBed configuration. If you need to spy on Router methods (like navigate), you can inject the Router instance from TestBed and spy on it directly instead.

2. Fixing the TypeError: Cannot read property 'subscribe' of undefined

This error happens because your AuthenticationService.login() method returns undefined in the test environment. In your component's onSubmit method, you call .subscribe() on the result of login()—but if that method doesn't return an Observable, you'll get this error.

Solution:

Mock the AuthenticationService properly so that its login method returns a valid Observable. Use Jasmine's createSpyObj to create a mock service with a spy for the login method, and make it return an Observable using of() from RxJS.

3. Bonus: Fix Duplicate @Component Decorator in Your Component

I noticed you have two @Component decorators on your LoginComponent—that's invalid Angular syntax! Merge them into a single decorator to avoid unexpected behavior.

Revised LoginComponent.ts (fixed decorator):

import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthenticationService } from '@/_services';
import { first } from 'rxjs/operators';
import { AuthentService } from '@/shared/_services/authentication/authentication.service';

@Component({ 
  selector: 'app-login', 
  templateUrl: './login.component.html', 
  styleUrls: ['./login.component.scss'] 
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  loading = false;
  submitted = false;
  returnUrl: string;
  error = '';

  constructor(
    private formBuilder: FormBuilder, 
    private route: ActivatedRoute, 
    private router: Router, 
    private authent: AuthentService, 
    private authenticationService: AuthenticationService
  ) {
    if (this.authenticationService.currentUserValue) {}
  }

  ngOnInit() {
    this.loginForm = this.formBuilder.group({ 
      email: ['', [Validators.required, Validators.email]], 
      password: ['', Validators.required] 
    });
  }

  get f() { return this.loginForm.controls; }

  onSubmit() {
    this.submitted = true;
    if (this.loginForm.invalid) { return; }
    const data = Object.assign({}, this.loginForm.value);
    this.authenticationService.login(data)
      .pipe(first())
      .subscribe(data => { 
        this.authenticationService.redirectPage(); 
      });
  }
}

Final Revised LoginComponent.spec.ts

Here's the updated test file with all fixes applied:

import { ActivatedRoute, Router } from '@angular/router';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AuthentService } from '@/shared/_services/authentication/authentication.service';
import { AuthenticationService } from './../../_services/authentication.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { LoginComponent } from './login.component';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '../../shared/shared.module';
import { of } from 'rxjs';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let authenticationService: jasmine.SpyObj<AuthenticationService>;
  let router: jasmine.SpyObj<Router>;
  const fakeActivatedRoute = { snapshot: { queryParams: { returnUrl: '/' } } };

  beforeEach(async(() => {
    // Create mock services
    const authServiceSpy = jasmine.createSpyObj('AuthenticationService', ['login', 'redirectPage']);
    // Mock login to return an Observable
    authServiceSpy.login.and.returnValue(of({}));

    await TestBed.configureTestingModule({
      imports: [ 
        ReactiveFormsModule, 
        FormsModule, 
        HttpClientTestingModule, 
        SharedModule, 
        RouterTestingModule.withRoutes([]) // Add empty routes to avoid warnings
      ],
      declarations: [LoginComponent],
      providers: [
        { provide: ActivatedRoute, useValue: fakeActivatedRoute },
        { provide: AuthenticationService, useValue: authServiceSpy },
        AuthentService // Mock this too if it has dependencies!
      ]
    }).compileComponents();

    // Get instances of mocked services for testing
    authenticationService = TestBed.inject(AuthenticationService) as jasmine.SpyObj<AuthenticationService>;
    router = TestBed.inject(Router) as jasmine.SpyObj<Router>;
    // Spy on router.navigate if you need to test navigation
    spyOn(router, 'navigate');
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    component.ngOnInit();
    fixture.detectChanges();
  });

  it('form invalid when empty', () => { 
    expect(component.loginForm.valid).toBe(false); 
  });

  it('username field validity', () => {
    const email = component.loginForm.controls.email;
    expect(email.valid).toBe(false);
    email.setValue('');
    expect(email.hasError('required')).toBeTruthy();
    email.setValue('invalid-email');
    expect(email.hasError('email')).toBeTruthy();
    email.setValue('test@example.com');
    expect(email.valid).toBeTruthy();
  });

  it('password field validity', () => {
    const password = component.loginForm.controls.password;
    expect(password.valid).toBeFalsy();
    password.setValue('');
    expect(password.hasError('required')).toBeTruthy();
    password.setValue('password123');
    expect(password.valid).toBeTruthy();
  });

  it('should call authenticationService.login on submit', () => {
    component.loginForm.setValue({ email: 'test@example.com', password: 'password123' });
    component.onSubmit();
    expect(authenticationService.login).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123' });
    expect(authenticationService.redirectPage).toHaveBeenCalled();
  });
});

Key Changes in the Test File:

  • Removed the conflicting Router provider, using RouterTestingModule.withRoutes([]) instead (empty routes avoid console warnings)
  • Created a mock AuthenticationService with createSpyObj, and made login return an Observable using of({})
  • Injected mocked services after TestBed configuration to use them in test cases
  • Added a test to verify login and redirectPage are called on form submit

Additional Notes:

  • If AuthentService has its own dependencies (like HttpClient), mock it similarly to AuthenticationService
  • Ensure all service dependencies are properly provided or mocked in the test module to avoid further injector errors

内容的提问来源于stack exchange,提问作者Avinash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:09:12