How to test Angular forms
Form validation and submission logic are critical parts of Angular applications that must be thoroughly tested to prevent user frustration and data quality issues. As the creator of CoreUI with over 12 years of Angular experience since 2014, I’ve built and tested complex forms in countless enterprise applications. Testing Angular forms involves verifying that form controls validate correctly, error messages appear at the right time, and submission handlers work as expected. The approach differs slightly between reactive forms and template-driven forms but both ensure form behavior reliability.
Test reactive forms by creating form controls, setting values, and asserting validation states and submission behavior.
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ReactiveFormsModule } from '@angular/forms'
import { LoginComponent } from './login.component'
describe('LoginComponent', () => {
let component: LoginComponent
let fixture: ComponentFixture<LoginComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LoginComponent],
imports: [ReactiveFormsModule]
}).compileComponents()
fixture = TestBed.createComponent(LoginComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create form with email and password controls', () => {
expect(component.loginForm.contains('email')).toBe(true)
expect(component.loginForm.contains('password')).toBe(true)
})
it('should validate email format', () => {
const emailControl = component.loginForm.get('email')
emailControl.setValue('invalid-email')
expect(emailControl.valid).toBe(false)
expect(emailControl.hasError('email')).toBe(true)
emailControl.setValue('[email protected]')
expect(emailControl.valid).toBe(true)
})
it('should require password with minimum length', () => {
const passwordControl = component.loginForm.get('password')
passwordControl.setValue('123')
expect(passwordControl.hasError('minlength')).toBe(true)
passwordControl.setValue('12345678')
expect(passwordControl.valid).toBe(true)
})
it('should disable submit when form is invalid', () => {
expect(component.loginForm.valid).toBe(false)
component.loginForm.patchValue({
email: '[email protected]',
password: '12345678'
})
expect(component.loginForm.valid).toBe(true)
})
it('should call onSubmit when form is submitted', () => {
spyOn(component, 'onSubmit')
component.loginForm.setValue({
email: '[email protected]',
password: '12345678'
})
const form = fixture.nativeElement.querySelector('form')
form.dispatchEvent(new Event('submit'))
expect(component.onSubmit).toHaveBeenCalled()
})
it('should display error message for invalid email', () => {
const emailControl = component.loginForm.get('email')
emailControl.setValue('invalid')
emailControl.markAsTouched()
fixture.detectChanges()
const errorElement = fixture.nativeElement.querySelector('.email-error')
expect(errorElement).toBeTruthy()
expect(errorElement.textContent).toContain('valid email')
})
})
Best Practice Note
Always test both valid and invalid states for each form control. Test that errors only appear after the control has been touched or the form submitted. Use patchValue for partial updates and setValue for complete form updates. Test custom validators thoroughly with edge cases. Mock HTTP services when testing form submission to avoid actual API calls. This is how we test forms in CoreUI for Angular—comprehensive coverage of validation rules, error display logic, and submission handlers to ensure robust user data collection.



