Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Volley from a Unit test? #346

Closed
davidfrancisandroidemul opened this issue Jun 25, 2020 · 7 comments
Closed

Using Volley from a Unit test? #346

davidfrancisandroidemul opened this issue Jun 25, 2020 · 7 comments

Comments

@davidfrancisandroidemul
Copy link

davidfrancisandroidemul commented Jun 25, 2020

Hi,

Not really an issue, more of a question.
I'm rather new to Android development so please bear with me.

TL;DR question is:
Can I use Volley from a JUnit test (which runs on my Macbook, not on the Android emulator)
and make a real Http call?

I'm using Kotlin if that makes any difference.

Problem I'm seeing is when I hit the line:

Volley.newRequestQueue(context, HurlStack())

I get an error:

java.lang.NoClassDefFoundError: org/apache/http/StatusLine
	at com.example.NetworkCallSampleTest.simpleVolleyTest(NetworkCallSampleTest.kt:37)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.ClassNotFoundException: org.apache.http.StatusLine
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:419)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	... 23 more

This is just a simple test to play around with the Volley library and make real http calls.
Am I allowed to use it like this?

Regards,

David

Edit: Volley version: com.android.volley:volley:1.1.1

@davidfrancisandroidemul
Copy link
Author

Forgot to say - if I write this as an 'instrumented test' then it works
AFAIK that runs on the emulator I have running

@jpd236
Copy link
Collaborator

jpd236 commented Jun 25, 2020

AFAIK it should work if HttpUrlConnection does. Obviously not a good practice to make real HTTP calls in a unit test but I don't see a reason Volley should cause problems here.

The stack trace doesn't make sense to me, because "Volley.newRequestQueue(context, HurlStack())" does not depend on org.apache.http.StatusLine. Are you somehow referencing this in your test? Do you have a full code sample demonstrating the problem?

@davidfrancisandroidemul
Copy link
Author

Yes I don't really want to leave it as a unit test, I just wanted to play around without having to deploy the app to the emulator.
I am somewhat baffled by the stack trace also.

Not sure whether it's something corrupted on my machine
Will try to write a minimum reproducible.

@davidfrancisandroidemul
Copy link
Author

Simple example is here: https://github.com/davidfrancisandroidemul/VolleySampleForGithub2

I noticed I'm having the same issue as in this stack overflow post:
https://stackoverflow.com/questions/50705527/volley-1-1-dependency-on-org-apache-http
Basically trying to create an instance of HurlStack causes the problem, when the apache lib is not in the classpath

@jpd236
Copy link
Collaborator

jpd236 commented Jun 26, 2020

Thanks for the sample code!

I don't understand why the JVM attempts to initialize this class. The only use of StatusLine in HttpStack is in the parent class performRequest method, which certainly isn't invoked at any point in this test. My understanding is that JVM classloading is generally lazy in practice, but that there are no strict guarantees in terms of how it decides to load classes, and so perhaps it's being too eager.

I can just add fake empty versions of these classes to the test project (StatusLine/BasicStatusLine, HttpEntity/BasicHttpEntity, HttpResponse/BasicHttpResponse) and use HurlStack.executeRequest without issue. (I also have to set testOptions.unitTests.returnDefaultValues = true to prevent calls to android.os.Log methods from failing).

It doesn't seem like it actually attempts to initialize these empty classes - if I add a static initializer block, it doesn't appear to run. But it's loading them.

Ultimately, the important thing is that this doesn't happen on Android devices, and it sounds like that's consistent with what you're seeing. For unit tests, unless someone can track down why these classes are loading and propose a fix for it in Volley, I think you'll need to do one of the following workarounds:

  • Add fake org.apache.http classes to your classpath so the classloader is happy. They won't be invoked by Volley unless you use the deprecated stack/methods, so what they contain doesn't seem to matter.
  • Use Robolectric, which appears to pull in the Apache HTTP library so the classes can be found.
  • Add a runtime-only dependency directly on the real Apache HTTP library to your test.

Closing this out as infeasible, but happy to take a closer look if someone can explain why this is happening in more detail and propose a fix.

@jpd236 jpd236 closed this as completed Jun 26, 2020
@davidfrancisandroidemul
Copy link
Author

Thanks for looking into this, sounds like a good place to leave it as you suggest.
I might link to this thread from the stackoverflow thread in case anyone else runs into this issue

@fargus9
Copy link

fargus9 commented Apr 16, 2021

Thanks for the sample code!

I don't understand why the JVM attempts to initialize this class. The only use of StatusLine in HttpStack is in the parent class performRequest method, which certainly isn't invoked at any point in this test. My understanding is that JVM classloading is generally lazy in practice, but that there are no strict guarantees in terms of how it decides to load classes, and so perhaps it's being too eager.

I can just add fake empty versions of these classes to the test project (StatusLine/BasicStatusLine, HttpEntity/BasicHttpEntity, HttpResponse/BasicHttpResponse) and use HurlStack.executeRequest without issue. (I also have to set testOptions.unitTests.returnDefaultValues = true to prevent calls to android.os.Log methods from failing).

It doesn't seem like it actually attempts to initialize these empty classes - if I add a static initializer block, it doesn't appear to run. But it's loading them.

Ultimately, the important thing is that this doesn't happen on Android devices, and it sounds like that's consistent with what you're seeing. For unit tests, unless someone can track down why these classes are loading and propose a fix for it in Volley, I think you'll need to do one of the following workarounds:

  • Add fake org.apache.http classes to your classpath so the classloader is happy. They won't be invoked by Volley unless you use the deprecated stack/methods, so what they contain doesn't seem to matter.
  • Use Robolectric, which appears to pull in the Apache HTTP library so the classes can be found.
  • Add a runtime-only dependency directly on the real Apache HTTP library to your test.

Closing this out as infeasible, but happy to take a closer look if someone can explain why this is happening in more detail and propose a fix.

Robolectric doesn't seem to load those classes, at least not in 4.5.1. I'm trying to shadow volley so that I can inject NetworkResponse objects for integration testing with the code that we've written at the request layer.

(edit)
Nevermind, including 'org.robolectric:shadows-httpclient:4.5.1' fixed the issue I was having.

monster1612 added a commit to zacharymenter/weather-or-not that referenced this issue Nov 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants