HTML Template
<input
  id="myFile"
  type="file"
  (change)="onFileSelected()"
  #fileInput
/>

Whenever the user selects a file, the change event calls the onFileSelected() method.

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

@Component({
  selector: 'app-input-file',
  templateUrl: './input-file.component.html'
})
export class InputFileComponent {

  uploadedFile!: File;

  onFileSelected(): void {

    const inputNode = document.querySelector('#myFile') as HTMLInputElement;

    if (inputNode.files && inputNode.files.length > 0) {
      this.uploadedFile = inputNode.files[0];
      console.log(this.uploadedFile);
    }

  }

}


How It Works
When the user selects a file:

  • The component locates the file input element.
  • The browser stores the selected file(s) in the files collection.
  • The first file is assigned to the uploadedFile property.
  • Although this works, Angular recommends avoiding direct DOM access where possible.

Writing the Unit Test
The browser normally populates the files property, so during unit testing we need to mock it ourselves.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputFileComponent } from './input-file.component';

describe('InputFileComponent', () => {

  let component: InputFileComponent;
  let fixture: ComponentFixture<InputFileComponent>;

  beforeEach(async () => {

    await TestBed.configureTestingModule({
      declarations: [InputFileComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(InputFileComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

  });

  it('should store the selected file', () => {

    const file = new File(
      ['Dummy Content'],
      'sample.txt',
      {
        type: 'text/plain'
      }
    );

    const input = fixture.nativeElement.querySelector('#myFile');

    Object.defineProperty(input, 'files', {
      value: [file]
    });

    input.dispatchEvent(new Event('change'));

    expect(component.uploadedFile).toEqual(file);

  });

});

Understanding the Test
Step 1: Create a Mock File

const file = new File(
  ['Dummy Content'],
  'sample.txt',
  {
    type: 'text/plain'
  }
);


This creates a fake File object that behaves exactly like a file selected by the user.

Step 2: Mock the Browser's files Property
Object.defineProperty(input, 'files', {
  value: [file]
});



Since the browser owns the files property, we replace it with our mock file during testing.

Step 3: Simulate File Selection
input.dispatchEvent(new Event('change'));

This triggers the same event that occurs when a user selects a file.

Step 4: Verify the Result

expect(component.uploadedFile).toEqual(file);

The test passes if the component correctly stores the selected file.

A Better Angular Approach
Instead of querying the DOM with document.querySelector(), Angular encourages passing the event object directly.
Template
<input
  type="file"
  (change)="onFileSelected($event)"
/>


Component
onFileSelected(event: Event): void {

  const input = event.target as HTMLInputElement;

  if (!input.files?.length) {
    return;
  }

  this.uploadedFile = input.files[0];

}


This approach is:

  • More Angular-friendly
  • Easier to unit test
  • Doesn't directly access the DOM
  • Better for maintainability

Unit Test for the Improved Version
it('should store the selected file', () => {

  const file = new File(
    ['Angular Testing'],
    'document.pdf',
    {
      type: 'application/pdf'
    }
  );

  const event = {
    target: {
      files: [file]
    }
  } as unknown as Event;

  component.onFileSelected(event);

  expect(component.uploadedFile).toEqual(file);

});


Notice that we no longer need to manipulate the DOM. We simply create a mock event object and call the component method directly, making the unit test cleaner and easier to understand.

Conclusion
Unit testing file uploads in Angular is straightforward once you know how to mock the browser's files property. While older implementations often relied on document.querySelector(), modern Angular applications should use the event object passed by the change event. This results in cleaner code, simpler unit tests, and components that are easier to maintain.

The overall testing strategy is simple:

  • Create a mock File object.
  • Assign it to the input's files property (or pass it through the event object).
  • Trigger the change event.
  • Verify that the component stores the selected file correctly.

Following this approach will help you confidently test file upload functionality in any Angular application.