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

Angular Jasmine测试中RXJS from()在concatMap()内未执行的问题求助

Why Your Clipboard Observable Isn't Executing in Jasmine

The core issue here is that the native navigator.clipboard.readText() API doesn’t work reliably in headless test environments like Jasmine/Karma. Clipboard operations require a real user interaction context and are often restricted for security reasons in test runners—meaning the promise returned by readText() never resolves (or rejects). This stalls your concatMap operator, so none of the subsequent operators (tap('read') or tap('timer 2')) ever run.

Here’s how to fix this:

Step 1: Mock the Clipboard API

Instead of relying on the real clipboard, mock navigator.clipboard.readText() to return a resolved promise with a test value. This lets your observable chain complete as expected.

Step 2: Use Jasmine’s Async Utilities to Wait for Timers

Your code uses timer(100), so you need to use fakeAsync and tick to simulate the passage of time in your test, ensuring all async operations finish before the test concludes.

Corrected Test Code

import { fakeAsync, tick } from '@angular/core/testing';

describe('CopyToClipboardTest', () => {
  let component: CopyToClipboardTest;

  beforeEach(() => {
    component = new CopyToClipboardTest();
    
    // Mock the clipboard readText method to return a test value
    spyOn(navigator.clipboard, 'readText').and.returnValue(Promise.resolve('test-clipboard-value'));
  });

  it('should copy input value and read from clipboard', fakeAsync(() => { 
    let capturedValue: string | undefined;
    let consoleLogs: string[] = [];
    
    // Spy on console.log to verify output (optional but helpful for debugging)
    spyOn(console, 'log').and.callFake((msg) => consoleLogs.push(msg));

    const click$ = component.clickButton('buttonElRef'); 
    click$.subscribe(value => { 
      capturedValue = value;
    }); 

    // Simulate 100ms passing to trigger the timer
    tick(100);

    // Verify the value was captured
    expect(capturedValue).toBe('test-clipboard-value');
    
    // Verify all expected console logs are present
    expect(consoleLogs).toEqual(['timer 1', 'read', 'timer 2']);
  }));
});

Optional: Mock the Button Click’s Copy Action

If your button’s click is supposed to write to the clipboard (which it likely is), you should also mock navigator.clipboard.writeText() to verify it’s called with the correct value:

beforeEach(() => {
  // Add this to your beforeEach
  spyOn(navigator.clipboard, 'writeText').and.returnValue(Promise.resolve());
});

// In your test, after tick(100):
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(/* expected value */);

This ensures your component is actually writing to the clipboard as intended, even if we’re mocking the read operation.

Why This Works

  • By mocking readText(), we avoid the real clipboard API’s restrictions in the test environment, ensuring the promise resolves immediately.
  • fakeAsync and tick let us control the passage of time, so the timer(100) operator emits and triggers the rest of your observable chain.

内容的提问来源于stack exchange,提问作者Get Off My Lawn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 21:12:30