Repository: robolectric/robolectric (Java)

Issue:
Improve error message for `Robolectric.buildActivity()` if provided an abstract class
**Is your feature request related to a problem? Please describe.**
A developer wanted help with their code producing the following error message

**Kotlin**
```kotlin
        val controller = Robolectric.buildActivity(AbstractClass::class.java)
```

**Stack Trace**
```
    java.lang.RuntimeException: error instantiating com.ichi2.anki.AbstractFlashcardViewer
        at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:440)
        at org.robolectric.Robolectric.buildActivity(Robolectric.java:113)
        at org.robolectric.Robolectric.buildActivity(Robolectric.java:78)
        at com.ichi2.anki.AbstractFlashcardViewerCommandTest.testRefreshIfRequired(AbstractFlashcardViewerCommandTest.kt:163)
        Caused by:
        java.lang.InstantiationException
            at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
            at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
            at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
            at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:438)
            ... 3 more
```

**Describe the solution you'd like**
The error message should state that the provided class is abstract (and that abstract classes are not supported)

**Describe alternatives you've considered**
N/A

**Additional context**

Sample `isAbstract` checking code

```java
java.lang.reflect.Modifier.isAbstract(clazz.getModifiers())
```

https://stackoverflow.com/a/1072895

https://github.com/robolectric/robolectric/blob/76048c014d1f29f5dd09c5504357cec24f050d8a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java#L446-L469


Discussion: @david-allison I have tried it with Java's abstract class, and compiler prohibit I pass the abstract class extends from `Activity` with `buildActivity`. Does Kotlin have differences?
seems reproducible in Java

```java
package com.ichi2.anki.dialogs.tags;

import android.app.Activity;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;

import androidx.test.ext.junit.runners.AndroidJUnit4;

@RunWith(AndroidJUnit4.class)
public class ActivityJavaTest {

    @Test
    public void robolectricTest() {
        Robolectric.buildActivity(TestActivity.class);
    }
}

abstract class TestActivity extends Activity {

}
```

<details>

```
Executing tasks: [:AnkiDroid:testAmazonDebugUnitTest, --tests, com.ichi2.anki.dialogs.tags.ActivityJavaTest] in project /Users/davidallison/StudioProjects/Anki-Android

...

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/davidallison/.gradle/caches/transforms-4/a1e787275c3dbd4ad78f089fa3bcbd13/transformed/slf4j-timber-3.1-runtime.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/davidallison/.gradle/caches/transforms-4/27e100f54975c2f39632cf7042c5c127/transformed/slf4j-timber-3.1/jars/classes.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [com.arcao.slf4j.timber.TimberLoggerFactory]

java.lang.RuntimeException: error instantiating com.ichi2.anki.dialogs.tags.TestActivity
	at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:457)
	at org.robolectric.Robolectric.instantiateActivity(Robolectric.java:448)
	at org.robolectric.Robolectric.buildActivity(Robolectric.java:120)
	at org.robolectric.Robolectric.buildActivity(Robolectric.java:85)
	at com.ichi2.anki.dialogs.tags.ActivityJavaTest.robolectricest(ActivityJavaTest.java:32)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:489)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$2(SandboxTestRunner.java:290)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:104)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:840)
	Suppressed: org.robolectric.android.internal.AndroidTestEnvironment$UnExecutedRunnablesException: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(Looper.getMainLooper()).idle() call.
Caused by: java.lang.InstantiationException
	at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:455)
	... 20 more


ActivityJavaTest > robolectricest FAILED
    java.lang.RuntimeException: error instantiating com.ichi2.anki.dialogs.tags.TestActivity
        at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:457)
        at org.robolectric.Robolectric.instantiateActivity(Robolectric.java:448)
        at org.robolectric.Robolectric.buildActivity(Robolectric.java:120)
        at org.robolectric.Robolectric.buildActivity(Robolectric.java:85)
        at com.ichi2.anki.dialogs.tags.ActivityJavaTest.robolectricest(ActivityJavaTest.java:32)
        Caused by:
        java.lang.InstantiationException
            at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
            at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
            at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
            at org.robolectric.util.ReflectionHelpers.callConstructor(ReflectionHelpers.java:455)
            ... 4 more
1 test completed, 1 failed
> Task :AnkiDroid:testAmazonDebugUnitTest FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':AnkiDroid:testAmazonDebugUnitTest'.
> There were failing tests. See the report at: file:///Users/davidallison/StudioProjects/Anki-Android/AnkiDroid/build/reports/tests/testAmazonDebugUnitTest/index.html
* Try:
> Run with --scan to get full insights.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 8s
62 actionable tasks: 12 executed, 50 up-to-date
16:42:35: Execution finished ':AnkiDroid:testAmazonDebugUnitTest --tests "com.ichi2.anki.dialogs.tags.ActivityJavaTest"'.
```

</details>
Thanks. I will try it again. Looks like Android  Studio gives me wrong hints.



Fix (patch):
diff --git a/robolectric/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java
index 6d43fc46ae7..e985d959ab9 100644
--- a/robolectric/src/main/java/org/robolectric/Robolectric.java
+++ b/robolectric/src/main/java/org/robolectric/Robolectric.java
@@ -20,6 +20,7 @@
 import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.View;
+import java.lang.reflect.Modifier;
 import javax.annotation.Nullable;
 import org.robolectric.android.AttributeSetBuilderImpl;
 import org.robolectric.android.AttributeSetBuilderImpl.ArscResourceResolver;
@@ -114,6 +115,9 @@ public static <T extends Activity> ActivityController<T> buildActivity(
     checkState(
         Thread.currentThread() == Looper.getMainLooper().getThread(),
         "buildActivity must be called on main Looper thread");
+    if (Modifier.isAbstract(activityClass.getModifiers())) {
+      throw new RuntimeException("buildActivity must be called with non-abstract class");
+    }
     return ActivityController.of(
         instantiateActivity(activityClass, intent), intent, activityOptions);
   }