zaterdag 5 september 2015

Robolectric and a Custom Shadow for Testing a Snackbar

Robolectric allows you to test your code and assess its correct behavior by using/providing Shadows. As with many things in Robolectric, a poorly documented part remains on how to create Custom Shadows.

Some will actually point out that it is bad practice to create your own Shadow classes, but what if you use libraries provided by Google, which are yet to be integrated in the list of Shadow-classes?

Side-note: take an example on the HttpClient class, it has been deprecated as of API level 23 (well, removed to be correct). But you can't run any tests with Robolectric 3.0 if you don't include the HttpClient. Quite a nuisance. Something needs to be done to keep Robolectric viable for the future.

One of the tests which I needed to complete is to test if a Snackbar notification is displayed when performing certain actions.

TL;DR

  • A Shadow class will mimic the behavior of the original class, and is extended with facilitators to be able to test the result accordingly.
  • You will need to load your ShadowClass as InstrumentedClass in order to get it working;
  • You'll need to apply some Reflection on the Snackbar to be able to instantiate an object (otherwise, you'll get these nasty StackOverflowError's)

The Custom Shadow Class


  • Your ShadowClass will be annotated with @Implements(Original.class). This will tell Robolectric which class you actually are Shadowing;
  • I've kept a list of Shadowed Snackbars as a static member, which allows to keep track of the objects which are created;
  • The Reflection will search for the Snackbar(Viewgroup group) constructor. This constructor is used when the Snackbar.make(...) method is invoked. When the constructor is found we'll modify the access from private to public by setting it accessible. We can then create a new instances with the found and updated constructor.
  • The newly created class is then 'shadowed' via the ShadowExtractor.extract(object) method, and not the ShadowOf_(object) as indicated in the documentation. This method actually doesn't exists anymore as of Robolectric 3.0.
  • Set the text on the ShadowSnackbar and keep a reference to it.

PS: this is just the first version of the code, I'll update it to make it a bit prettier.



ShadowSnackbar.java:

package be.acuzio.mrta.test.shadow;

import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;

import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.internal.ShadowExtractor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings({"UnusedDeclaration", "Unchecked"})
@Implements(Snackbar.class)
public class ShadowSnackbar {
    static List&lt:ShadowSnackbar> shadowSnackbars = new ArrayList<>();

    @RealObject
    Snackbar snackbar;

    String text;
    private int duration;
    private int gravity;
    private int xOffset;
    private int yOffset;
    private View view;

    @Implementation
    public static Snackbar make(@NonNull View view, @NonNull CharSequence text, int duration) {
        Snackbar snackbar = null;

        try {
            Constructor<Snackbar> constructor = Snackbar.class.getDeclaredConstructor(ViewGroup.class);

            //just in case, maybe they'll change the method signature in the future
            if (null == constructor)
                throw new IllegalArgumentException("Seems like the constructor was not found!");


            if (Modifier.isPrivate(constructor.getModifiers())) {
                constructor.setAccessible(true);
            }

            snackbar = constructor.newInstance(findSuitableParent(view));
            snackbar.setText(text);
            snackbar.setDuration(duration);
        } catch (Exception e) {
            e.printStackTrace();
        }

        shadowOf(snackbar).text = text.toString();

        shadowSnackbars.add(shadowOf(snackbar));

        return snackbar;
    }

    //this code is fetched from the decompiled Snackbar.class. 
    private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;

        do {
            if (view instanceof CoordinatorLayout) {
                return (ViewGroup) view;
            }

            if (view instanceof FrameLayout) {
//                if(view.getId() == 16908290) {
//                    return (ViewGroup)view;
//                }

                fallback = (ViewGroup) view;
            }

            if (view != null) {
                ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);

        return fallback;
    }

    // this is one of the methods which your actual Android code might invoke
    @Implementation
    public static Snackbar make(@NonNull View view, @StringRes int resId, int duration) {
        return make(view, view.getResources().getText(resId), duration);
    }


    //just a facilitator to get the shadow
    static ShadowSnackbar shadowOf(Snackbar bar) {
        return (ShadowSnackbar) ShadowExtractor.extract(bar);
    }

    //handy for when running tests, empty the list of snackbars
    public static void reset() {
        shadowSnackbars.clear();
    }

    //some non-Android related facilitators
    public static int shownSnackbarCount() {
        return shadowSnackbars.isEmpty() ? 0 : shadowSnackbars.size();

    }

    //taken from the modus-operandus of the ShadowToast
    //a facilitator to get the text of the latest created Snackbar
    public static String getTextOfLatestSnackbar() {
        if (!shadowSnackbars.isEmpty())
            return shadowSnackbars.get(shadowSnackbars.size() - 1).text;

        return null;
    }

    //retrieve the latest snackbar that was created
    public static Snackbar getLatestSnackbar() {
        if (!shadowSnackbars.isEmpty())
            return shadowSnackbars.get(shadowSnackbars.size() - 1).snackbar;

        return null;
    }
}

Robolectric Test Runner 

Add this ShadowClass to the custom build runner, otherwise you'll not be able to use the shadow or it won't be invoked when the code is tested.

RobolectricGradleTestRunner.java

package be.acuzio.mrta;

import android.support.design.widget.Snackbar;

import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.internal.bytecode.InstrumentationConfiguration;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.FileFsFile;
import org.robolectric.res.FsFile;

public class RobolectricGradleTestRunner extends RobolectricTestRunner {

    public RobolectricGradleTestRunner(Class testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        AndroidManifest appManifest = super.getAppManifest(config);
        FsFile androidManifestFile = appManifest != null ? appManifest.getAndroidManifestFile() : null;

        if (null != androidManifestFile && androidManifestFile.exists()) {
            return appManifest;
        } else {
            String moduleRoot = getModuleRootPath(config);

            androidManifestFile = FileFsFile.from(moduleRoot, "src/main/AndroidManifest.xml");
            FsFile resDirectory = FileFsFile.from(moduleRoot, "src/main/res/");
            FsFile assetDirectory = FileFsFile.from(moduleRoot, "src/main/assets");

            return new AndroidManifest(androidManifestFile, resDirectory, assetDirectory, "be.acuzio.mrta");
        }
    }

    private String getModuleRootPath(Config config) {
        String moduleRoot = config.constants().getResource("").toString().replace("file:", "");
        return moduleRoot.substring(0, moduleRoot.indexOf("/build"));
    }

    //override this method to add the Snackbar as instrumented class
    @Override
    public InstrumentationConfiguration createClassLoaderConfig() {
        InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
        builder.addInstrumentedClass(Snackbar.class.getName());

        return builder.build();
    }
}

Using the ShadowClass

Once you configured the ShadowClass, you'll be configure the test. Add the ShadowClass to the @Config annotation. You'll now be able to validate the invocation of the Snackbar objects via the ShadowSnackbar class.

package be.acuzio.mrta.test.view.activity;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Notification;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;

import junit.framework.Assert;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.shadows.ShadowNotificationManager;
import org.robolectric.shadows.ShadowToast;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;

...

@Config(sdk = 21,
        constants = be.acuzio.mrta.BuildConfig.class,
        manifest = Config.NONE,
        shadows = {ShadowSnackbar.class}
)
@RunWith(RobolectricGradleTestRunner.class)
public class EnrolmentActivitiesTest {
...
}

woensdag 15 juli 2015

HMAC One Time Password in C#

Quite recently I started developing apps for Windows Phone, a completely new experience for me, and a complete new mindset.

For the app I was creating, I needed to implement a way to create One-Time-Passwords (OTP's) using an HMAC algorithm. Although Nuget (and Google) provides quite some libraries and inspiration, I was not able to find a solution that actually fit my needs.

The idea of an HOTP algorithm is defined in this RFC: HOTP: An HMAC-Based One-Time Password Algorithm, so I won't tread into detail on the how-to's and why's of the HOTP.

After some browsing, blood, sweat and hacking, I came up with this solution:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;



namespace Acuzio.Util
{
    /// <summary>
    /// This enumeration contains the algorithms which are allowed to be used for rendering the One-time-password.
    /// </summary>
    public enum HashAlgorithm
    {
        SHA1,
        SHA256,
        SHA384,
        SHA512,
        MD5
    }


    public class OTP
    {
        private byte[] secret;
        private HashAlgorithm digest = HashAlgorithm.SHA512;
        private int digits = 8;
        //                                                 0  1   2    3     4      5       6        7         8 
        private readonly int[] DIGITS_POWERS = new int[] { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

        /// <summary>
        /// Create a new instance of the OTP class
        /// </summary>
        /// <param name="secret">The secret </param>
        public OTP(byte[] secret)
        {
            this.secret = secret;
        }

        /// <summary>
        /// Create a new instance of the OTP class
        /// </summary>
        /// <param name="secret">The secret </param>
        /// <param name="digits">The number of digits in the token </param>
        public OTP(byte[] secret, int digits)
        {
            this.secret = secret;
            this.digits = digits;
        }

        /// <summary>
        /// Create a new instance of the OTP class
        /// </summary>
        /// <param name="secret">The secret</param>
        /// <param name="digits">The number of digits in the token</param>
        /// <param name="digest">The algorithm to be used</param>
        public OTP(byte[] secret, int digits, HashAlgorithm digest)
        {
            this.secret = secret;
            this.digits = digits;
            this.digest = digest;
        }

        /// <summary>
        /// Generate a One-time-password for the provided challenge or movingfactor.
        /// </summary>
        /// <param name="input">The challenge which needs to be used as input</param>
        /// <returns>a new one time password</returns>
        public string generateOTP(UInt64 input)
        {
            if (this.digits <= 0 || this.digits > DIGITS_POWERS.Length - 1)
            {
                throw new ArgumentOutOfRangeException("digits", "The number of digits should be between 1 and 8");
            }

            String algorithm = null;

            switch (digest)
            {
                case HashAlgorithm.SHA1:
                    algorithm = MacAlgorithmNames.HmacSha1; break;
                case HashAlgorithm.SHA256:
                    algorithm = MacAlgorithmNames.HmacSha256; break;
                case HashAlgorithm.SHA384:
                    algorithm = MacAlgorithmNames.HmacSha384; break;
                case HashAlgorithm.SHA512:
                    algorithm = MacAlgorithmNames.HmacSha512; break;
                case HashAlgorithm.MD5:
                    algorithm = MacAlgorithmNames.HmacMd5; break;
                default:
                    throw new Exception("The provided algorithm is not supported");
            }

            MacAlgorithmProvider provider = MacAlgorithmProvider.OpenAlgorithm(algorithm);

            byte[] data = BitConverter.GetBytes(input);
            byte[] hmac = new byte[] { };

            if (BitConverter.IsLittleEndian)
                Array.Reverse(data);

            //create a hash for the known secret and append the challenge
            CryptographicHash hmacHasher = provider.CreateHash(CryptographicBuffer.CreateFromByteArray(this.secret));
            hmacHasher.Append(CryptographicBuffer.CreateFromByteArray(data));

            //create a hash and reset the hasher
            IBuffer buffer = hmacHasher.GetValueAndReset();
            CryptographicBuffer.CopyToByteArray(buffer, out hmac);

            int offset = hmac[hmac.Length - 1] & 0xf;

            int binary = ((hmac[offset + 0] & 0x7F) << 24) |
                        ((hmac[offset + 1] & 0xFF) << 16) |
                        ((hmac[offset + 2] & 0xFF) << 8) |
                        ((hmac[offset + 3] & 0xFF));

            int password = binary % DIGITS_POWERS[this.digits];

            //return the OTP with zero-padding for the number of desired digits
            return password.ToString(new string('0', this.digits));
        }
    }
}

Below an example on how to use the OTP class (e.g. unit tests).

using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Acuzio.Util;
using System.Text;

namespace TestProject
{
    [TestClass]
    public class OTPTest
    {
        //...

        [TestMethod]
        public void TestingSha512()
        {
            string secret = "SomeKey";
            byte[] key = Encoding.UTF8.GetBytes(secret);
            ulong interval = 66778;
            int returnDigits = 6;

            OTP generator = new OTP(key, returnDigits, HashAlgorithm.SHA512);
            String otp = generator.generateOTP(interval);

            Assert.IsNotNull(otp);
            Assert.AreEqual(6, otp.Length);
            Assert.AreEqual("539281", otp);
        }

        [TestMethod]
        public void TestingSha1()
        {
            string secret = "12345678901234567890";
            byte[] key = Encoding.UTF8.GetBytes(secret);
            ulong[] interval = new ulong[] { 0, 1, 2 };
            string[] expected = new string[] { "755224", "287082", "359152" };

            int returnDigits = 6;


            for (int i = 0; i < interval.Length; i++)
            {
                ulong nextInterval = interval[i];

                OTP generator = new OTP(key, returnDigits, HashAlgorithm.SHA1);
                String otp = generator.generateOTP(nextInterval);

                Assert.IsNotNull(otp);
                Assert.AreEqual(6, otp.Length);
                Assert.AreEqual(expected[i], otp);
            }
        }

        //...
    }
}

Some side notes:

  • Exception handling might be improved, but you can tailor it to your own needs;
  • I opted for defined class constructors in the OTP class, as we need to have a valid state of the object, before we can use it.
  • I used an array of powers, instead of using the pow-function, purely for speed.

woensdag 27 mei 2015

My Robolectric App - Upgraded to Robolectric 3.0 RC2

I finally managed to find some time to upgrade my Robolectric test application to Robolectric 3.0 RC2.

The updated code can be found on GitHub: https://github.com/kvandermast/my-robolectric-app.

Changes to the build.gradle script


First of all, we updated the Gradle Build tools and the Robolectric Gradle Plugin.
...
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
        classpath 'org.robolectric:robolectric-gradle-plugin:1.1.0'
    }
}
...

The name of the plugin has changed from 'robolectric' to 'org.robolectric', so that needed to be updated too.
...
apply plugin: 'com.android.application'
apply plugin: 'org.robolectric' //used to be 'robolectric'
...

Next up was the dependencies block: we updated to the latest versions of Robolectric, jUnit and Butterknife. Note that we changed the "androidTestCompile" to "testCompile", and added the "androidTestCompile" for Robolectric (seems to be required to the code compiled in your Android Studio).

...
    // ================== TESTING LIBRARIES ======================
    testCompile 'junit:junit:4.10'
    testCompile 'org.robolectric:robolectric:3.0-rc2'

    testCompile 'com.jakewharton:butterknife:6.1.0'

    androidTestCompile 'org.robolectric:robolectric:3.0-rc2'
...

The following section used to be the 'robolectric' task, which is now called "android.testOptions.unitTests.all".

...
android.testOptions.unitTests.all {
    // configure the set of classes for JUnit tests
    include '**/*Test.class'
    //exclude '**/espresso/**/*.class'

    // configure max heap size of the test JVM
    maxHeapSize = "2048m"
}
...

With these changes, you'll need to synch the project to get the latest libraries. With the upgrade from Robolectric 2.3 to 3.0, quite some changes were made to the library, resulting failed tests. The changes that were made are described in the following section.

Changes to tests

Using a custom RobolectricGradleTestRunnner class

We'll invoke a specific RobolectricTestRunnner which loads the AndroidManifest from the main project.

package be.acuzio.mrta.test;


import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.Fs;

public class RobolectricGradleTestRunner extends RobolectricTestRunner {

    public RobolectricGradleTestRunner(Class testClass) throws InitializationError {
        super(testClass);


    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        String manifestProperty = System.getProperty("android.manifest");
        AndroidManifest manifest;


        if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
            String resProperty = System.getProperty("android.resources");
            String assetsProperty = System.getProperty("android.assets");

            manifest = new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty),
                    Fs.fileFromPath(assetsProperty));

        } else {

            String myAppPath = RobolectricGradleTestRunner.class.getProtectionDomain()
                    .getCodeSource()
                    .getLocation()
                    .getPath();

            String manifestPath = myAppPath + "../../../src/main/AndroidManifest.xml";
            String resPath = myAppPath + "../../../src/main/res";
            String assetPath = myAppPath + "../../../src/main/assets";

            manifest = createAppManifest(Fs.fileFromPath(manifestPath), 
                                         Fs.fileFromPath(resPath),
                                         Fs.fileFromPath(assetPath));
        }

        return manifest;
    }
}


Changing the test configuration

The new version of Robolectric supports API levels higher than 18 (woot!). Unfortunately, something isn't quite working as expected on the new Robolectric version (you'll get ResourceNotFound exceptions, which only occur after upgrading from version 2.4 to 3.0). To fix this, pass the BuildConfig class (from your application module, not your test module) in the constants configuration.

...
@Config(emulateSdk = 21, constants = be.acuzio.mrta.BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
public class WidgetActivityTest {
...


Changing the Shadows

In version 2.4 we frequently used the Robolectric.getShadowApplication() method to fetch the Shadowed Application-class, e.g.
...
@Config(emulateSdk = 18)
@RunWith(RobolectricTestRunner.class)
public class MyBroadcastReceiverTest {
     ...

    /**
     * Let's first test if the BroadcastReceiver, which was defined in the manifest, is correctly
     * load in our tests
     */
    @Test
    public void testBroadcastReceiverRegistered() {
        List<ShadowApplication.Wrapper> registeredReceivers = Robolectric.getShadowApplication().getRegisteredReceivers();

        Assert.assertFalse(registeredReceivers.isEmpty());

        boolean receiverFound = false;
        for (ShadowApplication.Wrapper wrapper : registeredReceivers) {
            if (!receiverFound)
                receiverFound = MyBroadcastReceiver.class.getSimpleName().equals( 
                                    wrapper.broadcastReceiver.getClass().getSimpleName());
        }

        Assert.assertTrue(receiverFound); //will be false if the container did not register the broadcast receiver we want to test
    }
...

But in version 3.0, we need to fetch it from the ShadowApplication class, insteadof the Robolectric class:

...
@Config(emulateSdk = 21, constants = be.acuzio.mrta.BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
public class MyBroadcastReceiverTest {
    static {
        // redirect the Log.x output to stdout. Stdout will be recorded in the test result report
        ShadowLog.stream = System.out;
    }

    private ShadowApplication application;

    @Before
    public void setup() {
        this.application = ShadowApplication.getInstance();
    }

    /**
     * Let's first test if the BroadcastReceiver, which was defined in the manifest, is correctly
     * load in our tests
     */
    @Test
    public void testBroadcastReceiverRegistered() {
        //used to be:... registeredReceivers = Robolectric.getShadowApplication().getRegisteredReceivers();
        List<ShadowApplication.Wrapper> registeredReceivers = this.application.getRegisteredReceivers(); 

        Assert.assertFalse(registeredReceivers.isEmpty());
...

Running the tests

With Android Studio 1.2.1.1 (as of 1.1 I believe), it is - finally - possible to run the tests from within the IDE. Previously we needed to run the test from the console like so:

$ ./gradlew testDebug

Which worked perfectly, unless you had a lot of test and little patience to seem them executed one by one.

You can now use the "Unit test" Test Artificat in the build variants.


Running a test is now quite easy, just press "CTRL+R" when in the Test Class, or select "Run '' " from the Context Menu when you right-click the Unit Test.