Advanced Testing

Back

Loading concept...

🧪 Angular Advanced Testing: Your Secret Superpower

Imagine you’re a detective with a magnifying glass, checking every corner of your app to make sure everything works perfectly. That’s what testing is!


🎭 The Story: Meet Detective Angular

Once upon a time, there was a brilliant detective named Angular. Detective Angular had built an amazing city (your app), but needed to make sure every building, every road, and every traffic light worked perfectly.

To do this, Detective Angular used seven magical tools. Let’s discover each one!


🔮 Tool 1: Async Testing Utilities

What’s the Problem?

Sometimes your app does things that take time—like fetching data from the internet. It’s like ordering pizza: you don’t get it instantly!

The Magic Solution

Angular gives you special helpers to wait for these slow things:

// fakeAsync: Pretend time machine!
it('should load data', fakeAsync(() => {
  let result = '';

  fetchData().then(data => {
    result = data;
  });

  // Fast-forward time!
  tick(1000);

  expect(result).toBe('Hello!');
}));

The Three Magic Words

Helper What It Does
fakeAsync Creates a fake time zone
tick() Fast-forwards time
flush() Finishes ALL waiting tasks
graph TD A["Start Test"] --> B["fakeAsync Zone"] B --> C["Do Async Stuff"] C --> D["tick - Skip Time"] D --> E["Check Results"] E --> F["✅ Test Complete"]

Real-World Example

it('shows loading then data',
  fakeAsync(() => {
    component.loadUsers();

    // Still loading...
    expect(component.loading)
      .toBe(true);

    // Skip 2 seconds
    tick(2000);

    // Now loaded!
    expect(component.users.length)
      .toBeGreaterThan(0);
}));

🌐 Tool 2: HTTP Testing

The Story

Imagine your app needs to call a restaurant (server) to get food (data). But during testing, we don’t want to call the REAL restaurant. What if it’s closed? What if it’s slow?

The Solution: Fake Restaurant!

// Setup the fake restaurant
let httpMock: HttpTestingController;

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [MyService]
  });

  httpMock = TestBed.inject(
    HttpTestingController
  );
});

Making Fake Orders

it('gets users from API', () => {
  const fakeUsers = [
    { id: 1, name: 'Ana' },
    { id: 2, name: 'Bob' }
  ];

  // Make the request
  service.getUsers().subscribe(users => {
    expect(users.length).toBe(2);
  });

  // Catch the request
  const req = httpMock.expectOne(
    '/api/users'
  );

  // Send fake response
  req.flush(fakeUsers);
});

Testing Errors Too!

it('handles server error', () => {
  service.getUsers().subscribe({
    error: (err) => {
      expect(err.status).toBe(500);
    }
  });

  const req = httpMock.expectOne(
    '/api/users'
  );

  // Simulate error!
  req.flush('Oops!', {
    status: 500,
    statusText: 'Server Error'
  });
});
graph TD A["Test Starts"] --> B["Service Makes Request"] B --> C["httpMock Catches It"] C --> D["You Send Fake Response"] D --> E["Service Gets Fake Data"] E --> F["✅ Test Passes"]

🧭 Tool 3: Router Testing

The Challenge

Your app has many pages (routes). How do you test if clicking a button takes you to the right page?

The RouterTestingModule

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [
      RouterTestingModule.withRoutes([
        { path: 'home', component: Home },
        { path: 'about', component: About }
      ])
    ]
  });
});

Testing Navigation

it('navigates to about page',
  fakeAsync(() => {
    const router = TestBed.inject(Router);
    const location = TestBed.inject(Location);

    // Create component
    const fixture = TestBed
      .createComponent(AppComponent);

    // Initialize router
    router.initialNavigation();

    // Navigate!
    router.navigate(['about']);
    tick();

    // Check location
    expect(location.path())
      .toBe('/about');
}));

Testing Route Guards

it('blocks unauthorized access',
  fakeAsync(() => {
    // User not logged in
    authService.isLoggedIn = false;

    router.navigate(['admin']);
    tick();

    // Should redirect to login
    expect(location.path())
      .toBe('/login');
}));

🎭 Tool 4: Mock Patterns

What’s a Mock?

A mock is like a stunt double in movies. It looks like the real thing but is safer and easier to control!

The Spy Pattern

// Create a spy on a method
spyOn(userService, 'getUser')
  .and.returnValue(of({
    id: 1,
    name: 'Test User'
  }));

// Now getUser() returns
// our fake data!

The Stub Pattern

// Full fake service
const mockUserService = {
  getUser: () => of({
    id: 1,
    name: 'Fake'
  }),
  saveUser: () => of(true)
};

TestBed.configureTestingModule({
  providers: [
    {
      provide: UserService,
      useValue: mockUserService
    }
  ]
});

The Mock Class Pattern

class MockUserService {
  users = [{ id: 1, name: 'Ana' }];

  getUser(id: number) {
    return of(
      this.users.find(u => u.id === id)
    );
  }

  saveUser(user: any) {
    this.users.push(user);
    return of(true);
  }
}
graph TD A["Real Service"] --> B{Testing?} B -->|Yes| C["Use Mock"] B -->|No| D["Use Real"] C --> E["Fast & Predictable"] D --> F["Slow & Unpredictable"]

🎮 Tool 5: Component Harnesses

The Problem with DOM Testing

Finding buttons and inputs in HTML is messy:

// Old way - fragile! 😰
const button = fixture.nativeElement
  .querySelector('button.submit-btn');

If someone changes the class name, your test breaks!

The Harness Solution

// Setup harness loader
let loader: HarnessLoader;

beforeEach(() => {
  fixture = TestBed.createComponent(App);
  loader = TestbedHarnessEnvironment
    .loader(fixture);
});

Using Built-in Harnesses

it('clicks mat-button', async () => {
  // Find button by harness
  const button = await loader
    .getHarness(MatButtonHarness);

  // Click it!
  await button.click();

  // Check result
  expect(component.clicked)
    .toBe(true);
});

Finding Specific Elements

it('fills form', async () => {
  // Find input by label
  const input = await loader.getHarness(
    MatInputHarness.with({
      placeholder: 'Enter name'
    })
  );

  // Type in it!
  await input.setValue('Ana');

  expect(component.name).toBe('Ana');
});

🏃 Tool 6: Shallow Testing

Deep vs Shallow

Deep Testing Shallow Testing
Renders child components Ignores children
Slow Fast
Tests integration Tests isolation

The CUSTOM_ELEMENTS_SCHEMA Trick

TestBed.configureTestingModule({
  declarations: [ParentComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
});

This tells Angular: “Don’t worry about unknown tags!”

The NO_ERRORS_SCHEMA Option

TestBed.configureTestingModule({
  declarations: [MyComponent],
  schemas: [NO_ERRORS_SCHEMA]
});

Even more relaxed—ignores ALL unknown things.

When to Use Shallow Testing

graph TD A["What to Test?"] --> B{Just This Component?} B -->|Yes| C["Shallow Test ⚡"] B -->|No| D{With Children?} D -->|Yes| E["Deep Test 🐢"] D -->|No| F["Unit Test 🎯"]

Example

@Component({
  template: `
    <h1>{{ title }}</h1>
    <app-child></app-child>
    <app-footer></app-footer>
  `
})
class ParentComponent {
  title = 'Hello';
}

// Shallow test - doesn't need
// Child or Footer components!
it('shows title', () => {
  const h1 = fixture.nativeElement
    .querySelector('h1');
  expect(h1.textContent).toBe('Hello');
});

🔧 Tool 7: Test Provider Overrides

The Power of Overrides

Sometimes you need to swap out a service JUST for one test. Here’s how!

overrideComponent

TestBed.overrideComponent(
  MyComponent,
  {
    set: {
      providers: [
        {
          provide: DataService,
          useClass: MockDataService
        }
      ]
    }
  }
);

overrideModule

TestBed.overrideModule(
  SharedModule,
  {
    remove: {
      exports: [HeavyComponent]
    },
    add: {
      exports: [LightComponent]
    }
  }
);

overrideProvider

TestBed.overrideProvider(
  API_URL,
  { useValue: 'http://test.local' }
);

Real-World Example

describe('UserProfile', () => {
  it('shows offline message', () => {
    // Override just for this test!
    TestBed.overrideProvider(
      ConnectionService,
      {
        useValue: {
          isOnline: () => false
        }
      }
    );

    fixture = TestBed.createComponent(
      UserProfile
    );

    expect(fixture.nativeElement
      .textContent)
      .toContain('You are offline');
  });
});

🎯 Quick Summary

Tool Purpose Key Method
Async Utils Test slow stuff fakeAsync, tick
HTTP Testing Fake server calls HttpTestingController
Router Testing Test navigation RouterTestingModule
Mocks Fake dependencies spyOn, useValue
Harnesses Easy DOM access loader.getHarness()
Shallow Testing Fast unit tests CUSTOM_ELEMENTS_SCHEMA
Provider Overrides Swap services overrideProvider

🚀 You’re Now a Testing Hero!

graph TD A["🎯 You"] --> B["Know Async Testing"] A --> C["Master HTTP Mocks"] A --> D["Navigate Router Tests"] A --> E["Create Perfect Mocks"] A --> F["Use Harnesses"] A --> G["Write Fast Shallow Tests"] A --> H["Override Providers"] B & C & D & E & F & G & H --> I["🏆 Testing Champion!"]

Remember: Every great app has great tests! Now go make your code bulletproof! 💪

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.