components icon indicating copy to clipboard operation
components copied to clipboard

bug(MatSnackBarHarness): Testing a snack bar with a duration

Open ersimont opened this issue 4 years ago • 8 comments

Reproduction

https://stackblitz.com/edit/components-issue-v9512z?file=src%2Fapp%2Fexample-test.spec.ts

Steps to reproduce:

  1. Try to e.g. await harness.getMessage() a snack bar that was opened with a duration. You can see this simply by following the link above, which will run that automatically.

Expected Behavior

The harness returns the message in the snack bar.

Actual Behavior

The test hangs for the duration that the snack bar is open, then fails with "Failed to find element".

Environment

  • Angular: 9.1.5
  • CDK/Material: 9.2.2
  • Browser(s): Chrome
  • Operating System (e.g. Windows, macOS, Ubuntu): Windows

Commentary

Using the same test code, I can test a snack bar that was opened without a duration. But if my production code does use a duration it does not appear possible to test it with the harness. Am I missing something? I assume assigning a duration is a common use-case.

ersimont avatar May 08 '20 11:05 ersimont

I can confirm this is still an issue with

  • Angular: 11.1.1
  • Material: 11.1.1

stijnvn avatar Mar 10 '21 09:03 stijnvn

I am having this issue as well, any ideas as to a work around?

bluemagma612 avatar Jun 11 '21 20:06 bluemagma612

My "workaround" involves using a whole test library (that I wrote). It runs test in the fakeAsync zone and does not flush timeouts before searching for harnesses. You can use the AngularContext for testing e.g. services, and ComponentContext for components.

For an example that uses MatSnackBar, you can see this test

ersimont avatar Jun 15 '21 00:06 ersimont

The issue is still present with:

  • Angular 13.2.5
  • Material 13.2.5

Trying to retrieve the harness with await loader.getHarness(MatSnackBarHarness) fails with:

Error: Failed to find element matching one of the following queries:
(MatSnackBarHarness with host element matching selector: ".mat-snack-bar-container")

Also, as described ersimont, the test is still hanging for the duration the snackbar is open.

If it's not planned to fix it any time soon, it would be nice to indicate in the doc that this Harness is broken to avoid people wasting time on it, and wondering why their test is failing (what I experimented this morning).

qlassalle avatar Mar 12 '22 11:03 qlassalle

The issue is still present with Angular v15 and the MDC-based Snack Bar.

dkimmich-onventis avatar Apr 12 '23 10:04 dkimmich-onventis

I had the same issue on Angular 14.2.12 and Material 14.2.7. Getting the error:

Error: Failed to find element matching one of the following queries:
(MatSnackBarHarness with host element matching selector: ".mat-snack-bar-container")

when using await loader.getHarness(MatSnackBarHarness)

ashwynnair avatar Apr 14 '23 17:04 ashwynnair

It still had the same issue on Angular 16.2.12 and Material 16.2.14, and I got a workaround to fix it by simply wrap harness code block into fakeAsync:

it('can test snack bars', async () => {
    fixture.componentInstance.sayHello();
    fakeAsync(() => {
      const snackBar = await loader.getHarness(MatSnackBarHarness);
      expect(await snackBar.getMessage()).toBe('Hello!');
    })();  // must invoke the returned function
  });

shawn40611 avatar Apr 29 '24 17:04 shawn40611

I ran into this problem recently and I've come up with a solution which I believe is easy and fast. As a note: My test environment uses Jest, but I think other testing frameworks would also work in a similar way.

Instead of using a harness to get the snackbar, just spy on it instead.

    snackSpy = jest.spyOn(TestBed.inject(MatSnackBar), 'open').mockReturnValue(null)
    // important to return null, otherwise the test will wait for the snackbar to close

You can then test the snackbar opening with specific values in a few ways

    // test everything
    expect(snackSpy).toHaveBeenCalledWith('Some message', 'Close', { duration: 2000 })

    // only test the message
    expect(snackSpy.mock.lastCall[0]).toEqual('abc123')

You might also have some success experimenting with jest.useFakeTimers() but that might be getting a bit too specific for this question.

blockerdude avatar May 06 '24 18:05 blockerdude