Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a shadow class to work around crashes related to custom attributes when testing an Android app with Robolectric?

I'm integrating a custom widget into my project. It uses custom attributes and is crashing Robolectric. From what I gather, custom attributes aren't supported yet. Note: the constructor is "hostile" because it throws exceptions on construction, but I've commented them out for now.

Crash Log

java.lang.RuntimeException: error inflating layout/main at com.xtremelabs.robolectric.res.ViewLoader.inflateView(ViewLoader.java:106) at com.xtremelabs.robolectric.res.ViewLoader.inflateView(ViewLoader.java:82) at com.xtremelabs.robolectric.res.ViewLoader.inflateView(ViewLoader.java:86) at com.xtremelabs.robolectric.res.ResourceLoader.inflateView(ResourceLoader.java:377) at com.xtremelabs.robolectric.shadows.ShadowLayoutInflater.inflate(ShadowLayoutInflater.java:43) at com.xtremelabs.robolectric.shadows.ShadowLayoutInflater.inflate(ShadowLayoutInflater.java:48) at android.view.LayoutInflater.inflate(LayoutInflater.java) at com.xtremelabs.robolectric.shadows.ShadowActivity.setContentView(ShadowActivity.java:101) at android.app.Activity.setContentView(Activity.java) at com.blah.MainActivity.onCreate(MainActivity.java:17) at com.blah.MainActivityTest.setUp(MainActivityTest.java:29) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)

Caused by: java.lang.ClassCastException: com.blah.support.shadows.ShadowMultiDirectionSlidingDrawer cannot be cast to com.xtremelabs.robolectric.shadows.ShadowView at com.xtremelabs.robolectric.Robolectric.shadowOf(Robolectric.java:857) at com.xtremelabs.robolectric.shadows.ShadowViewGroup.addView(ShadowViewGroup.java:70) at android.view.ViewGroup.addView(ViewGroup.java) at com.xtremelabs.robolectric.shadows.ShadowViewGroup.addView(ShadowViewGroup.java:60) at android.view.ViewGroup.addView(ViewGroup.java) at com.xtremelabs.robolectric.res.ViewLoader$ViewNode.addToParent(ViewLoader.java:217) at com.xtremelabs.robolectric.res.ViewLoader$ViewNode.create(ViewLoader.java:180) at com.xtremelabs.robolectric.res.ViewLoader$ViewNode.inflate(ViewLoader.java:150) at com.xtremelabs.robolectric.res.ViewLoader$ViewNode.inflate(ViewLoader.java:153) at com.xtremelabs.robolectric.res.ViewLoader.inflateView(ViewLoader.java:102) ... 29 more

I'm trying to work around this issue, because I don't care too much about testing this widget. Basically I want my tests not to crash and to verify that the view element is appearing on the screen.

Someone suggested a hack of placing the java file in a android package, but I'm not sure if it applies in my case. As suggested by this answer, the custom widget lives in package "android" which is parallel to my com.blah structure.

I created a shadow of the widget to get around the hostility issue (but currently I just comment out the exception throwing). Originally I wanted to bypass the work that was being done in the constructor since it relies on attributes that Robolectric isn't reporting correctly. The shadow constructor is getting called, but it continues through normal constructor execution. Is there a way to bypass additional construction?

ShadowClass

@Implements (MultiDirectionSlidingDrawer.class)
public class ShadowMultiDirectionSlidingDrawer
{
    public void __constructor__( Context context, AttributeSet attrs )
    {
    }

    public void __constructor__( Context context, AttributeSet attrs, int defStyle )
    {
    }
}

Custom Test Runner

public class CustomTestRunner extends RobolectricTestRunner 
{
    public CustomTestRunner( Class<?> testClass ) throws InitializationError
    {
        super( testClass );
        addClassOrPackageToInstrument("android");
    }

    @Override
    protected void bindShadowClasses()
    {
        super.bindShadowClasses();
        Robolectric.bindShadowClass( ShadowMultiDirectionSlidingDrawer.class );
    }
}

Is this the right approach to work around the crashes until the testing framework supports this? Am I doing something wrong with my shadow or am I missing something else?

like image 513
colabug Avatar asked Jul 27 '12 21:07

colabug


1 Answers

So I solved this a simple way. Instead of inflating the view as part of my main.xml, I put it in a separate layout file. In my Activity's constructor, I inflated the layout in a protected function. In my test class, I extended the class under test and instead of inflating the view element, I used a boolean to track that the functions as called.

MainActivity.java

public class MainActivity extends FragmentActivity
{
    @Override
    public void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        addOptionsShade();
    }

    protected void addOptionsShade()
    {
        ViewGroup viewGroup = (ViewGroup) findViewById( R.id.main_view );
        View view = getLayoutInflater().inflate( R.layout.options_shade, null );
        viewGroup.addView( view );
    }
}

MainActivityTest.java

@Test
public void shouldHaveOptionsShade() throws Exception
{
    assertTrue( mainActivity.hostileLibraryWasCalled );
}

class TestMainActivity extends MainActivity
{
    boolean hostileLibraryWasCalled = false;

    @Override
    protected void addOptionsShade()
    {
        hostileLibraryWasCalled = true;
    }
}

I removed the shadow class & bindings and put the library back in a sensible place (not in android package).

like image 144
colabug Avatar answered Oct 14 '22 10:10

colabug