Reputation: 811
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);
}
}
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
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. :)
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-virtual
s).
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
Reputation: 16695
There are a couple of ways to do this, and which one you choose depends what you're trying to test.
You can mock the IPhone interface (as you have done in your commented out code.
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