问题
I just upgraded to Robolectric 2.1.1 and integrated Picasso today. I now have two test cases that fail randomly (one of these fragments doesn't even use Picasso). If I keep running the tests, everything usually ends up passing (might take a few tries).
Test
@Before
public void setUp() throws Exception
{
detailActivity = Robolectric.buildActivity( ActivityUnderTest.class )
.withIntent( createIntent() )
.create()
.start()
.resume()
.get();
// Note: The other test case doesn't use the fancy withIntent() doohickey
}
public static Intent createIntent()
{
Bundle bundle = DetailFragment.createBundle( getTestData() );
Intent intent = new Intent( new ActivityUnderTest(), Activity.class );
intent.putExtras( bundle );
return intent;
}
@Test
public void shouldNotBeNull() throws Exception
{
assertNotNull( detailActivity );
}
Codez
I am sending in a bundle of information to the activity, which is basically an empty shell. The activity displays a Fragment via XML. In my Fragment, I get the data from the bundle using getActivity().getIntent().getExtras()
.
Lying Warning
[WARN] You're instantiating an activity (com.colabug.project.singlepanel.ActivityUnderTest) directly; consider using Robolectric.buildActivity() instead.
Stacktrace
java.lang.RuntimeException: An unexpected exception occurred
at com.squareup.picasso.Request$1.run(Request.java:114)
at org.robolectric.util.Scheduler$PostedRunnable.run(Scheduler.java:162)
at org.robolectric.util.Scheduler.runOneTask(Scheduler.java:107)
at org.robolectric.util.Scheduler.advanceTo(Scheduler.java:92)
at org.robolectric.util.Scheduler.advanceToLastPostedRunnable(Scheduler.java:68)
at org.robolectric.util.Scheduler.unPause(Scheduler.java:25)
at org.robolectric.shadows.ShadowLooper.unPause(ShadowLooper.java:219)
at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:258)
at org.robolectric.util.ActivityController.invokeWhilePaused(ActivityController.java:202)
at org.robolectric.util.ActivityController.start(ActivityController.java:144)
at com.colabug.project.singlepanel.DetailActivityTest.setUp(DetailActivityTest.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
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)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:241)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.NullPointerException: Bitmap config was null.
at org.robolectric.shadows.ShadowBitmap.getBytesPerPixel(ShadowBitmap.java:338)
at org.robolectric.shadows.ShadowBitmap.getRowBytes(ShadowBitmap.java:225)
at org.robolectric.shadows.ShadowBitmap.getByteCount(ShadowBitmap.java:230)
at android.graphics.Bitmap.getByteCount(Bitmap.java)
at com.squareup.picasso.Utils$BitmapHoneycombMR1.getByteCount(Utils.java:250)
at com.squareup.picasso.Utils.getBitmapBytes(Utils.java:65)
at com.squareup.picasso.Stats.processBitmap(Stats.java:64)
at com.squareup.picasso.Stats.bitmapDecoded(Stats.java:40)
at com.squareup.picasso.Picasso.loadFromType(Picasso.java:365)
at com.squareup.picasso.Picasso.resolveRequest(Picasso.java:215)
at com.squareup.picasso.Picasso.run(Picasso.java:197)
at com.squareup.picasso.Request.run(Request.java:108)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:680)
at com.squareup.picasso.Utils$PicassoThread.run(Utils.java:244)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.colabug</groupId>
<artifactId>Project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>apk</packaging>
<name>Project</name>
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<!-- Make sure this is below the android dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.robolectric</groupId>
<artifactId>robolectric</artifactId>
<version>2.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.picasso</groupId>
<artifactId>picasso</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<!-- See http://code.google.com/p/maven-android-plugin/ -->
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>maven-android-plugin</artifactId>
<version>2.8.3</version>
<configuration>
<sdk>
<platform>17</platform>
</sdk>
</configuration>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
Any ideas? IntelliJ might be the cause since it shows up in the stacktrace. I have used mvn clean
and manually cleaned up auto generated files on the command line and rebuilt the project in IntelliJ.
回答1:
Drew is correct. The NPE is occurring in a background thread.
Caused by: java.lang.NullPointerException: Bitmap config was null.
Fundamentally this seems to be an issue with ShadowBitmap
. However the solution I opted for was to create a MockPicasso
class.
The stub implementation prevents the NPE. It also has the added benefit of preventing the unit tests from requesting bitmaps over the network in the first place.
MockPicasso.java
package com.squareup.picasso;
import android.graphics.Bitmap;
import android.widget.ImageView;
public class MockPicasso extends Picasso {
private static String lastImagePath = null;
private static ImageView lastTargetImageView = null;
MockPicasso() {
super(null, null, null, Cache.NONE, null, new MockStats());
}
public static void init() {
singleton = new MockPicasso();
}
public static String getLastImagePath() {
return lastImagePath;
}
public static ImageView getLastTargetImageView() {
return lastTargetImageView;
}
@Override
public RequestBuilder load(String path) {
lastImagePath = path;
return new MockRequestBuilder();
}
class MockRequestBuilder extends RequestBuilder {
@Override
public void into(ImageView target) {
lastTargetImageView = target;
}
}
static class MockStats extends Stats {
MockStats() {
super(Cache.NONE);
}
@Override
void bitmapDecoded(Bitmap bitmap) {
// Do nothing.
}
}
}
Using getLastImagePath()
and getLastTargetImageView()
you can test your code is requesting the correct image and loading it in the correct view without actually hitting the network.
MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView avatar = (ImageView) findViewById(R.id.avatar);
String path = getIntent().getStringExtra("avatar_url");
Picasso.with(this).load(path).into(avatar);
}
MyActivityTest.java
@Test
public void shouldDisplayAvatar() throws Exception {
MockPicasso.init();
String imageUrl = "http://www.example.com/test.jpg";
Intent intent = new Intent().putExtra("avatar_url", imageUrl);
MyActivity myActivity = Robolectric.buildActivity(MyActivity.class)
.create().get();
myActivity.setIntent(intent);
Robolectric.shadowOf(myActivity).callOnCreate(null);
ImageView avatar = (ImageView) myActivity.findViewById(R.id.avatar);
assertThat(MockPicasso.getLastImagePath()).isEqualTo(imageUrl);
assertThat(MockPicasso.getLastTargetImageView()).isSameAs(avatar);
}
回答2:
The warning isn't lying.
Intent intent = new Intent( new ActivityUnderTest(), Activity.class );
The "new ActivityUnderTest()" qualifies as "instantiating an activity directly."
In many cases where tests fail/pass seeming-randomly, there is a threading issue in the underlying code. The last time this happened to me the "sometimes this fails" test was actually exposing a race condition in my queue code.
I find that when I have the reaction "this test is stupid!" I almost discover that the test is actually smarter than me (and nicer than me, too, as it is trying to be helpful).
来源:https://stackoverflow.com/questions/17096891/spurious-test-failures-using-picasso-and-robolectric