I\'m trying to implement some unit tests for a couple of classes that rely on WifiManager and the returned ScanResults. What I\'d like to do is be able to control the ScanR
You could try to create the ScanResult instances by using reflection to access the private constructors. The code might look something like this:
try {
Constructor<ScanResult> ctor = ScanResult.class.getDeclaredConstructor(null);
ctor.setAccessible(true);
ScanResult sr = ctor.newInstance(null);
sr.BSSID = "foo";
sr.SSID = "bar";
// etc...
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
For other ways of testing, I most of times convert the information from instances like ScanResult and encapsulate only the information I need into my own objects. These I feed to the method doing the hard work. This makes testing easier as you can easily build these intermediate objects without relying on the real ScanResult objects.
I've been struggling for a while to build ScanResult
object. I have successfully used that reflection approach above.
If somebody is searching for a way to clone ScanResult
object (or any other object implementing Parcelable
interface) you can use this approach (I checked it right in a unit test):
@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public class MovingAverageQueueTests {
@Test
public void parcelTest() {
Parcel parcel = Parcel.obtain();
ScanResult sr = buildScanResult("01:02:03:04:05:06", 70);
parcel.writeValue(sr);
parcel.setDataPosition(0); // required after unmarshalling
ScanResult clone = (ScanResult)parcel.readValue(ScanResult.class.getClassLoader());
parcel.recycle();
assertThat(clone.BSSID, is(equalTo(sr.BSSID)));
assertThat(clone.level, is(equalTo(sr.level)));
assertThat(clone, is(not(sameInstance(sr))));
}
private ScanResult buildScanResult(String mac, int level) {
Constructor<ScanResult> ctor = null;
ScanResult sr = null;
try {
ctor = ScanResult.class.getDeclaredConstructor(null);
ctor.setAccessible(true);
sr = ctor.newInstance(null);
sr.BSSID = mac;
sr.level = level;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return sr;
}
}
And as for performance, this naive check:
@Test
public void buildVsClonePerformanceTest() {
ScanResult sr = null;
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
sr = buildScanResult("01:02:03:04:05:06", 70);
}
long elapsedNanos = System.nanoTime() - start;
LOGGER.info("buildScanResult: " + elapsedNanos);
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
sr = cloneScanResult(sr);
}
elapsedNanos = System.nanoTime() - start;
LOGGER.info("cloneScanResult: " + elapsedNanos);
}
Showed these results:
Oct 26, 2016 3:25:19 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFO: buildScanResult: 202072179 Oct 26, 2016 3:25:21 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFO: cloneScanResult: 2004391903
So cloning this way is 10 times less effective than creating instance even with reflection. I know this test is not robust as optimizations are done while compiling... However factor of ten is difficult to mitigate. I did also tested 10K iterations and then the factor was even 100! Just for your information.
P.S. getting Parcel.obtain() and parcel.recycle out of loop doesn't help
Create an abstraction around WifiManager. Use this for your mocking. Mocking stuff you don't own is hard and brittle. If done right you should be able to switch the internals, plus you'll end up with a better mockable API.
For your testing you can stub/fake the manager to you hearts content. For production you'll pass in a concrete instance.
With regards to your point about changing your code just to make it testable that is incorrect. Firstly you should mock roles not types as discussed in the paper below. Google for more info.
Secondly creating an abstraction around third party code is a best practice as stated by the dependency inversion principle in SOLID. You should always depend on abstractions rather than concrete implementations whether you are unit testing or not.
http://www.objectmentor.com/resources/articles/dip.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod