mkul
mkul

Reputation: 811

Mock class which inherit from abstract class and implements interface

Assume following scenario: I have a PhoneController class which use Phone class. Phone is a class which inherit from abstract class Device and it implements IPhone interface. For testing PhoneController I want to mock Phone class, but I don't know how it might be done using NSubstitute, because Phone class inherits abstract class and additionally it implements interface.

Example code:

public abstract class Device
{
    protected string Address { get; set; }
}

public interface IPhone
{
    void MakeCall();
}

public class Phone : Device, IPhone
{
    public void MakeCall()
    {
        throw new NotImplementedException();
    }
}

public class PhoneController
{
    private Phone _phone;

    public PhoneController(Phone phone)
    {
        _phone = phone;
    }
}

[TestClass]
public class PhoneControllerTests
{
    [TestMethod]
    public void TestMethod1()
    {
        // How mock Phone class? 
        //var mock = Substitute.For<Device, IPhone>();

        //usage of mock
        //var controller = new PhoneController(mock);
    }
}

Second scenario:

Controller uses GetStatus method from Device abstract class, so _phone cannot be changed to IPhone type

public abstract class Device
{
    protected string Address { get; set; }

    public abstract string GetStatus();
}

public interface IPhone
{
    void MakeCall();
}

public class Phone : Device, IPhone
{
    public void MakeCall()
    {
        throw new NotImplementedException();
    }

    public override string GetStatus()
    {
        throw new NotImplementedException();
    }
}

public class PhoneController
{
    private Phone _phone;

    public PhoneController(Phone phone)
    {
        _phone = phone;
    }

    public string GetDeviceStatus()
    {
        return _phone.GetStatus();
    }

    public void MakeCall()
    {
        _phone.MakeCall();
    }
}

[TestClass]
public class PhoneControllerTests
{
    [TestMethod]
    public void TestMethod1()
    {
        // How mock Phone class? 
        //var mock = Substitute.For<Device, IPhone>();

        //usage of mock
        //var controller = new PhoneController(mock);
    }
}

Upvotes: 3

Views: 5549

Answers (2)

David Tchepak
David Tchepak

Reputation: 10464

If PhoneController takes a Phone, then you will have to use a Phone or a subclass of Phone. Using Substitute.For<Device, IPhone>() will generate a type a bit like this:

public class Temp : Device, IPhone { ... }

Which is not the same as a Phone.

So there are a few options. Ideally I'd consider @pm_2's suggestion of making PhoneController take an IPhone instead. By making it depend on an interface rather than something concrete we gain some flexibility: PhoneController now can work with anything that adheres to the IPhone interface, including a mocked IPhone instance. This also means we can change the behaviour of production code by have other implementations (contrived example, maybe an EncryptedPhone : IPhone that encrypts the call, without requiring a change to PhoneController).

If you do need to couple the specific Phone class to PhoneController, then mocking immediately becomes more difficult. After all, you are stating "this only works with this specific class", then trying to get it working with another class (the substituted class). For this approach, if you are able to make all the relevant members of the Phone class virtual then you can create a substitute using Substitute.For<Phone>(). If you need to also need to run some of the original Phone code, but substitute other parts, then you can using Substitute.ForPartsOf<Phone>() as @pm_2 suggested. Remember that NSubstitute (and many other .NET mocking libraries) will not mock non-virtual members, so keep this in mind when mocking classes.

Finally, it is worth considering not mocking Phone at all and using the real class instead. If PhoneController depends on the details of specific Phone implementation, then testing it with a fake version is not going to tell you whether it works. If instead it only needs a compatible interface, then it is a good candidate for using a substitute. Mocking libraries automate the creation of an alternative type to use with a class, but they will not automate having a design that accommodates the use of that type. :)

Edit for second scenario

I think my previous answer still applies for the additional scenario. To recap: my first approach is to attempt to decouple the PhoneController from a specific implementation; then I'd consider substituting for Phone directly (with the disclaimer about non-virtual methods). And I'd always keep in mind not mocking at all (this should probably be the first option).

There are many ways we can achieve the first option. We could update PhoneController to take an IDevice (extract interface from Device) and an IPhone, with the constructor PhoneController(IPhone p, IDevice d). The real code can then be:

var phone = new Phone();
var controller = new PhoneController(phone, phone);

While the test code could be:

var phone = Substitute.For<IPhone>();
var device = Substitute.For<IDevice>();
var testController = new PhoneController(phone, device);
// or
var phone = Substitute.For<IPhone, IDevice>();
var testController = new PhoneController(phone, (IDevice) phone);

Alternatively we could create IPhone, IDevice and then have:

interface IPhoneDevice : IPhone, IDevice { }
public class Phone : IPhoneDevice { ... }

I've seen interface inheritance like this discouraged before, so you may want to look into that first before taking this option.

You can also combine these approaches, by replacing IDevice in the examples above with your current Device base class and mocking that (disclaimer: non-virtuals).

I think the main question you need to answer is how to you want to couple PhoneController to its dependencies? Think about this in terms of what concrete dependencies does it need, and what can be expressed in terms of logical interfaces. The answer to that will determine your options for testing.

Upvotes: 2

Paul Michaels
Paul Michaels

Reputation: 16695

There are a couple of ways to do this, and which one you choose depends what you're trying to test.

  1. You can mock the IPhone interface (as you have done in your commented out code.

  2. You can subclass the Phone class (either manually, or use NSubstitute's .ForPartsOf<>). See here for a blog post on this.

I find that if I structure my test using the Arrange/Act/Assert method, it's clearer what I'm trying to test (ideally there should be a single call in your Act section; for example:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var mock = Substitute.For<IPhone>();
    var controller = new PhoneController(mock);

    // Act
    int result = controller.Method();

    // Assert
    Assert.Equal(result, 3);
}

EDIT - Based on updated comments

You can't unit test an abstract class, because that class doesn't contain (by definition) any code. In your scenario, it looks like what you're trying to do is to test the concrete Phone class. If that's the case, then simply create an instance of the Phone class and test it; you don't need to involve the controller:

// Arrange
var phone = new Phone();

// Act
string result = phone.GetStatus();

// Assert
Assert.Equal("New", result);

Upvotes: 4

Related Questions