ChatGPT解决这个技术问题 Extra ChatGPT

Android JUnit Testing ... How to Expect an Exception

I'm attempting to write some tests using the built-in android Junit testing framework. I am running into a problem with a test where I am expecting an exception to be thrown. In JUnit, the annotation for the test method would be:

@Test(expected = ArithmeticException.class)

However, in Android, this test fails with an ArithmeticException.

I understand that the Android implementation is only a subset of JUnit 3, and doesn't even allow the annotation @Test (must be @SmallTest, @MediumTest, or @LargeTest, and none of those allow for the 'expected=..' parameter), but this seems like a fairly significant test, and seems like the Android testing framework would be seriously lacking if it did not have this feature.

Note : I tested this by adding the JUnit jar to the project and by adding and the annotations to my test methods. It makes sense to me why the annotations would be completely ignored because the Android framework (runner?) is not looking for that annotation and just ignores it. Basically, I'm just looking for the 'right' way to do this within the framework.


S
Steve Lancashire

The standard junit 3 idiom for this sort of test was:

public void testThatMethodThrowsException()
{
  try
  {
    doSomethingThatShouldThrow();
    Assert.fail("Should have thrown Arithmetic exception");
  }
  catch(ArithmeticException e)
  {
    //success
  }
}

This idiom also works in JUnit4 when you want to check the state of something after the exception is thrown. You can't really assert anything when using the "expected" idiom after the exception is thrown.
You should unmark this response as the right one and check the one of sandrstar, which is a better way.
s
sandrstar

Now JUnit4 is available via Android SDK (refer to android-test-kit)

Update: it's official now on d.android.com:

The AndroidJUnitRunner is a new unbundled test runner for Android, which is part of the Android Support Test Library and can be downloaded via the Android Support Repository. The new runner contains all improvements of GoogleInstrumentationTestRunner and adds more features: JUnit4 support Instrumentation Registry for accessing Instrumentation, Context and Bundle Arguments Test Filters @SdkSupress and @RequiresDevice Test timeouts Sharding of tests RunListener support to hook into the test run lifecycle Activity monitoring mechanism ActivityLifecycleMonitorRegistry

So, JUnit4 style of exception testing using expected annotation:

@Test(expected= IndexOutOfBoundsException.class) 
public void empty() { 
     new ArrayList<Object>().get(0); 
}

or expected exception rules:

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
    List<Object> list = new ArrayList<Object>();

    thrown.expect(IndexOutOfBoundsException.class);
    thrown.expectMessage("Index: 0, Size: 0");
    list.get(0); // execution will never get past this line
}

is also possible.

Refer to official documentation for more details on how to setup test support library.


Does this work for you with unit tests? i.e. in the project test directory as opposed to the androidTest directory?
@Louth yes, actually I'm using it mostly without gradle and it works fine most of the time. I haven't tried eclipse, but IntelliJ Idea is fine. You can use maven or just get jar from your Android SDK installation (unpack aar from extras\android\m2repository\com\android\support\test\testing-support-lib\0.1).
Yeah cheers @sandrstar. This is a pretty old thread and yes, using Android Studio this is all pretty straightforward now. :)
L
Ludvig W

I've been looking for some good solutions, however, none of the solutions was really satisfying to me. So, I created my own.

public final void assertThrows(VoidFunction v, Class<? extends Exception> e) {
    try {
        v.call();
    } catch (Exception ex) {
        if (!ex.getClass().equals(e)) {
            Assert.fail();
        }
        // Do nothing, basically succeeds test if same exception was thrown.
        return;
    }

    // Fails if no exception is thrown by default.
    Assert.fail();
}

Where VoidFunction is a simple interface:

@FunctionalInterface
public interface VoidFunction {
    void call();
}

This is used as follows (for example):

@Test
public void testFoo() {
    assertThrows(() -> foo.bar(null)), NullPointerException.class);
    assertThrows(() -> foo.setPositiveInt(-5)), IllegalArgumentException.class);
    assertThrows(() -> foo.getObjectAt(-100)), IndexOutOfBoundsException.class);
    assertThrows(new VoidFunction() {
            @Override
            public void call() {
                foo.getObjectAt(-100);
            }
        }, IndexOutOfBoundsException.class); // Success
    assertThrows(new VoidFunction() {
                @Override
                public void call() {
                    throw new Exception();
                }
            }, NullPointerException.class); // Fail

}

I included one call without using the lambda, this makes it easier to understand code sometimes, at least for me. Simple to use and it allows multiple exception catches in ONE method.