如何测试来自future的异常

21

继续昨天的问题,我该如何测试异步方法是否会抛出异常。

main(){
  test( "test2", () async {
    expect( await throws(), throwsException);
  });

}

Future throws () async {
  throw new FormatException("hello");
}

1
未来的读者,你可能正在寻找expectLater(),请向下滚动... - Chuck Batson
7个回答

20

使用try-catch

最可靠的方法是使用try-catch块来明确捕获异常并确保方法已经完成运行。

try {
    await methodWhichThrows();
    fail("exception not thrown");
} catch (e) {
    expect(e, new isInstanceOf<...>());
    // more expect statements can go here
}
该方法的优点在于可以对异常值执行其他检查。
`throwsA`只能作为最后一条语句使用。
仅使用`expect`本身仅在测试的最后一条语句中有效。无法控制方法何时抛出异常,因此如果语句(包括后续调用`expect`)假定异常已经被抛出,则可能存在竞争条件。
expect(methodWhichThrows(), throwsA(new isInstanceOf<...>())); // unreliable unless last

它可以被使用,但你必须非常小心地记住它适用的情况和不适用的情况。所以,与其针对不同情况使用不同的方法,还不如坚持使用try-catch方法更为安全。

演示

下面的完整示例演示了竞态条件对这两种方法的影响:

import 'dart:async';
import 'package:test/test.dart';

//----------------------------------------------------------------
/// This approach to expecting exceptions is reliable.
///
Future reliableApproach(int luck) async {
  expect(await setValueAndReturnsHalf(42), equals(21));
  expect(state, equals(Evenness.isEven));

  try {
    await setValueAndReturnsHalf(3);
    fail("exception not thrown");
  } catch (e) {
    expect(e, new isInstanceOf<ArgumentError>());
  }

  // Expect value to be odd after execption is thrown.

  await shortDelay(luck); // in my experience there's no such thing called luck
  expect(state, equals(Evenness.isOdd));
}

//----------------------------------------------------------------
/// This approach to expecting exceptions is unreliable.
///
Future unreliableApproach(int luck) async {
  expect(await setValueAndReturnsHalf(42), equals(21));
  expect(state, equals(Evenness.isEven));

  expect(setValueAndReturnsHalf(3), throwsA(new isInstanceOf<ArgumentError>()));

  // Expect value to be odd after execption is thrown.

  await shortDelay(luck); // luck determines if the race condition is triggered
  expect(state, equals(Evenness.isOdd));
}

//----------------------------------------------------------------

enum Evenness { isEven, isOdd, inLimbo }

int value = 0;
Evenness state = Evenness.isEven;

/// Sets the [value] and [state].
///
/// If the [newValue] is even, [state] is set to [Evenness.isEven] and half of it
/// is returned as the Future's value.
///
/// If the [newValue] is odd, [state] is set to [Evenness.isOdd] and an exception
/// is thrown.
///
/// To simulate race conditions, this method takes 2 seconds before it starts
/// processing and 4 seconds to succeed or throw an exception. While it is
/// processing, the [state] is set to [Evenness.inLimbo].
///
Future<int> setValueAndReturnsHalf(int newValue) async {
  await shortDelay(2);

  state = Evenness.inLimbo;

  await shortDelay(2);

  value = newValue;

  if (newValue % 2 != 0) {
    state = Evenness.isOdd;
    throw new ArgumentError.value(newValue, "value", "is not an even number");
  } else {
    state = Evenness.isEven;
    return value ~/ 2;
  }
}

/// Delays used to simulate processing and race conditions.
///
Future shortDelay(int seconds) {
  var c = new Completer();
  new Timer(new Duration(seconds: seconds), () => c.complete());
  return c.future;
}

/// Examples of the reliable and unreliable approaches.
///
void main() {
  test("Correct operation when exception is not thrown", () async {
    expect(await setValueAndReturnsHalf(42), equals(21));
    expect(value, equals(42));
  });

  group("Reliable approach:", () {
    test("works when there is bad luck", () async {
      // 1 second = bad luck, future returning function not started processing yet
      await reliableApproach(1);
    });

    test("works when there is more bad luck", () async {
      // 3 second = bad luck, future returning function still processing
      await reliableApproach(3);
    });

    test("works when there is good luck", () async {
      // 5 seconds = good luck, future returning function definitely finished
      await reliableApproach(5);
    });
  });

  group("Unreliable approach:", () {
    test("race condition encountered by bad luck", () async {
      // 1 second = bad luck, future returning function not started processing yet
      await unreliableApproach(1);
    });

    test("race condition encountered by more bad luck", () async {
      // 3 second = bad luck, future returning function still processing
      await unreliableApproach(3);
    });

    test("race condition avoided by good luck", () async {
      // 5 seconds = good luck, future returning function definitely finished
      await unreliableApproach(5);
    });
  });
}

2
你也可以使用 await expectLater(..., throwsA(...))。这样可以等待异步期望,避免不知道操作何时完成的问题。 - simolus3

13

它的工作方式如下:

import 'package:test/test.dart';
import 'dart:async';

void main() {
  test( "test2", ()  { // with or without `async`
    expect(throws(), throwsA(const TypeMatcher<FormatException>()));
  });
}

Future throws () async {
  throw new FormatException("hello");
}

基本上只需删除await。测试框架可以处理Future无论成功与否。


我认为 expectAsync 在这里会很有帮助。 - ashokd
1
你为什么这样想?不应该必要的。 - Günter Zöchbauer
因为“throws()”方法是异步的。 - ashokd
这会使它更加复杂。ExpectAsync用于检查某些异步代码是否已执行。像我的答案一样简单地检查异常即可。 - Günter Zöchbauer
为了更多的例子,我已经添加了一个带有exceptAsync的答案。 - ashokd

7
官方文档中使用 expectLaterthrowsA 来做这件事的示例非常好,以下是示例。 官方文档
void functionThatThrows() => throw SomeException();

void functionWithArgument(bool shouldThrow) {
  if (shouldThrow) {
    throw SomeException();
  }
}

Future<void> asyncFunctionThatThrows() async => throw SomeException();

expect(functionThatThrows, throwsA(isA<SomeException>()));

expect(() => functionWithArgument(true), throwsA(isA<SomeException>()));

var future = asyncFunctionThatThrows();
await expectLater(future, throwsA(isA<SomeException>()));

await expectLater(
    asyncFunctionThatThrows, throwsA(isA<SomeException>()));

1
最简单和最短的回答是:

expect(throws(), throwsException)

测试异常/错误类型:

expect(throws(), throwsA(predicate((e) => e is MyException)));

0

经过多次尝试和错误,我发现这个可以按预期工作:

test('fetch SHOULD throw exception WHEN api fail with exception', () {
  when(clientMock.get(uri)).thenAnswer((_) async => throw Exception());
  expect(() => sut.fetch(), throwsA(isInstanceOf < Exception > ()));
});

0

我对其他答案都不满意,太啰嗦了。

所以简短版的是:

test('ArgumentError is thrown when throwIt is true', () {
   expectLater(() => myAsyncFunctionThatTakesAnArgument(throwIt: true), throwsA(isA<ArgumentError>()));
});

0

有多种方法可以测试来自Future的错误。如果“throws without async”方法抛出异常,则Gunter的答案将起作用。下面的示例将处理来自future方法的异常。

import 'package:test/test.dart';
import 'dart:async';

void main() {
    test("test with Zone", () {
        runZoned(() {
            throws();
        }, onError: expectAsync((e, s) {
            expect(e, new isInstanceOf<FormatException>());
        }));
    });

    test('test with future catch error', () {
        throws().catchError(expectAsync((e) {
            expect(e, new isInstanceOf<FormatException>());
        }));
    });
}

Future throws() async{
    Completer completer = new Completer();
    completer.complete(new Future(() => throw new FormatException("hello")));
    return completer.future;
}

1
我的版本与你的throws()方法一样有效。 - Günter Zöchbauer
@GünterZöchbauer 是的,这是几种额外的做法。 - ashokd
1
@GünterZöchbauer的答案更简单和更简洁。 - Jon Scalet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接