Unit Testing with Xamarin.Forms DependencyService
21 January 2017

Unit Testing with Xamarin.Forms DependencyService

Xamarin.Forms has it own DependencyService implemented, fairly easy to use and fast, so you don't need to implement another one. But it lacks constructor dependency injection. That makes hard to mock our services in unit tests, as the ViewModel is resolving the dependencies internally. But there is a way to easily overcome this situation without loosing functionality.

How DependencyService works on Xamarin.Forms?

As with almost any other dependency container out there, we simply start declaring our service with an Interface and a class implementing it:

public interface ICryptoService
{
    string GetMD5StringBase64(string data);
}

 

public class CryptoService : ICryptoService
{
    public string GetMD5StringBase64(string data)
    {
        var strintToReturn = AwesomeMD5StringGenerator();
        return strintToReturn;
    }
}

But we don't need to register both of them in a container, we only need to add an attribute to our implementation so it gets registered in Xamarin.Forms DependencyService

[assembly: Xamarin.Forms.Dependency(typeof(Demo.App.Droid.Services.CryptoService))]

This way, whenever we ask for ICryptoService to Xamarin.Forms DependencyService, we would get the associated dependency class registered:

this.cryptoService = DependencyService.Get<ICryptoService>();

This makes extremely easy to have a shared interface with platform implementations, as the correct implementation is automatically registered with the dependency attribute.

But, as DependencyService doesn't have a mechanism to resolve dependencies automatically using constructor injection, we would get in trouble when start to test our ViewModels.

What's the problem you would find while Unit Testing?

While creating unit tests for your ViewModels you try to isolate the code you want to test to avoid fighting with external dependencies you aren't interested at that moment. Traditionally you can use any Mock framework to mock dependencies and inject that mocked objects to your viewmodel using the constructor. But since Xamarin.Forms doesn't support constructor injection, you can't take that approach. Instead you would need to abstract the use of the DependencyService to something you can customize for your tests.

How we can resolve this without throwing away Xamarin.Forms DependencyService?

We are going to create an interface called IDependencyService in our core library project, that expose one method to get one dependency from a type:

public interface IDependencyService
{
    T Get<T>() where T : class;
}

And will create the default implementation, also in our core library project, called CustomDependencyService:

public class CustomDependencyService : IDependencyService
{
    public T Get<T>() where T : class
    {
        return DependencyService.Get<T>();
    }
}

As you can see, this class only job is to hide the real DependencyService with an interface and a custom class.

Now we are going to create a new implementation of IDependencyService in our Test project. Call it MockDependencyService:

public class MockDependencyService : IDependencyService
{
    private readonly IDictionary<Type, object> registeredServices = new Dictionary<Type, object>();
    
    public void Register<T>(T implementation)
    {
        this.registeredServices.Add(typeof(T), implementation);
    }
    
    public T Get<T>() where T : class
    {
        return (T)this.registeredServices[typeof(T)];
    }
}

Actually here is the magic, in our tests project we implement the IDependencyService Get method, but also we add a dictionary, where we can store our services interface and implementation and a register method. We don't use Xamarin.Forms DependencyService here but a simple dictionary, as we are not testing the dependencies nor the dependency service, that would do the job.

Now when initializing our tests, we can register all needed services in our mock dependency service, creating Mock implementations with MoQ, RhinoMock or your favourite mock system. In this post we are using MoQ:

[ClassInitialize]
public static void ClassInit(TestContext context)
{
    dependencyService = new MockDependencyService();
 
    connectionService = new Mock<IConnectionService>();
    dialogService = new Mock<IDialogService>();
 
    dependencyService.Register(connectionService.Object);
    dependencyService.Register(dialogService.Object);
}

Finally we "only" need a way of telling our viewmodels to use this instance of IDependencyService instead of the regular DependencyService. The easier way to do this is using a parametrized constructor and a default constructor. 

When your ViewModel is instantiated by the app to add it as BindingContext of the page, the default empty parameters constructor will be used. In this constructor we create manually an instance of DependencyService class we make before, so all dependencies are resolved using an implementation of IDependencyService:

public ProfileViewModel() : this(new CustomDependencyService())
{
}

Then, we add a new constructor with an IDependencyService parameter that we will use in our tests to inject our Mocked dependency service instance:

public ProfileViewModel(IDependencyService dependencyService) : base(dependencyService)
{
    this.settingsService = customDependencyService.Get<ISettingsService>();
    this.webApiProvider = customDependencyService.Get<IWebApiProvider>();
}

In our case, customDependencyService is a private member of our ViewModelBase. The default empty constructor send a new instance of our DependencyService to the parametrized constructor. This way you don't need to write the constructor logic twice.

Now in our TestInitialize method, let's create the ViewModel instance, using the parametrized constructor:

[TestInitialize]
public void TestInit()
{
    viewModel = new ProfileViewModel(dependencyService);
}

And here it is, now you can use all your dependencies with MoQ, without needing to test all the way down to them, and being able to focus on your subject under test, your viewmodel. 

Final thoughts

Here i had a dilema when implementing this. I don't like to be forced to modify my ViewModels only to be able to test them. But in the other hand, i don't want to be forced to use another extra third party component only to be able to test my ViewModels, so this solution was a bit of a compromise between keeping my third party libraries count as low as possible and don't add excesive complexity or odd things to the ViewModels.

Hope it is of help for someone!

Keep coding!

Related

0 ( 0 reviews)

Post a Comment