Если PhoneController
принимает Phone
, то вам придется использовать Phone
или подкласс Phone
.Использование Substitute.For<Device, IPhone>()
сгенерирует тип, подобный следующему:
public class Temp : Device, IPhone { ... }
Это не то же самое, что Phone
.
Так что есть несколько вариантов.В идеале я рассмотрел бы предложение @ pm_2 о том, чтобы вместо PhoneController
взять IPhone
.Заставляя его зависеть от интерфейса, а не от чего-то конкретного, мы получаем некоторую гибкость: PhoneController
теперь может работать со всем, что соответствует интерфейсу IPhone
, включая поддельный экземпляр IPhone
.Это также означает, что мы можем изменить поведение производственного кода с помощью других реализаций (надуманный пример, может быть EncryptedPhone : IPhone
, который шифрует вызов, не требуя изменения на PhoneController
).
Если вам нужно связать определенный класс Phone
с PhoneController
, то насмешка сразу становится более сложной.В конце концов, вы заявляете «это работает только с этим конкретным классом», а затем пытаетесь заставить его работать с другим классом (замещенным классом).Для этого подхода, если вы можете сделать все соответствующие члены Phone
класса virtual
, вы можете создать замену, используя Substitute.For<Phone>()
.Если вам также нужно выполнить какой-то оригинальный код Phone
, но заменить другие части, то вы можете использовать Substitute.ForPartsOf<Phone>()
, как @ pm_2 предложил .Помните, что NSubstitute (и многие другие библиотеки .NET для насмешек) не будут издеваться над не virtual
членами, так что имейте это в виду, когда насмехаетесь над классами.
Наконец, стоит подумать о том, чтобы вообще не насмехаться Phone
и используя реальный класс вместо этого.Если PhoneController
зависит от деталей конкретной реализации Phone
, то тестирование ее с поддельной версией не скажет вам, работает ли она.Если вместо этого ему нужен только совместимый интерфейс, то он является хорошим кандидатом для использования замены.Библиотеки-насмешки автоматизируют создание альтернативного типа для использования с классом, но они не будут автоматизировать создание дизайна, который бы соответствовал использованию этого типа.:)
Изменить для второго сценария
Я думаю, что мой предыдущий ответ все еще применим для дополнительного сценария.Напомним: мой первый подход - попытаться отделить PhoneController
от конкретной реализации;тогда я бы подумал о замене Phone
напрямую (с оговоркой о не virtual
методах).И я бы всегда помнил, что вообще не надо насмехаться (это, вероятно, должно быть первым вариантом).
Есть много способов достичь первого варианта.Мы можем обновить PhoneController
, чтобы получить IDevice
(извлекать интерфейс из Device
) и IPhone
, с конструктором PhoneController(IPhone p, IDevice d)
.Тогда реальный код может быть:
var phone = new Phone();
var controller = new PhoneController(phone, phone);
В то время как тестовый код может быть:
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);
В качестве альтернативы мы можем создать IPhone
, IDevice
и затем иметь:
interface IPhoneDevice : IPhone, IDevice { }
public class Phone : IPhoneDevice { ... }
Я видел, что наследование интерфейса, как это раньше, не поощрялось, поэтому вы можете сначала изучить его, прежде чем использовать эту опцию.
Вы также можете объединить эти подходы, заменив IDevice
вПриведенные выше примеры с вашим текущим Device
базовым классом и насмешкой над этим (заявление об отказе: не- virtual
с).
Я думаю, что основной вопрос, на который вам нужно ответить, - как вы хотите соединить PhoneController
к его зависимостям?Подумайте об этом с точки зрения того, какие конкретные зависимости ему нужны, и что можно выразить в терминах логических интерфейсов.Ответ на этот вопрос определит ваши варианты тестирования.