NUnit Mocking non funziona per il metodo Singleton
Domanda
Abbi pazienza, sono nuovo di NUnit. Vengo dalla terra di Rails, quindi parte di questo è nuovo per me.
Ho una riga di codice simile a questa:
var code = WebSiteConfiguration.Instance.getCodeByCodeNameAndType("CATALOG_Brands_MinQty", item.Catalog);
Sto cercando di deriderlo, in questo modo (supponiamo che code
sia già inizializzato):
var _websiteConfigurationMock = new DynamicMock(typeof(WebSiteConfiguration));
_websiteConfigurationMock.ExpectAndReturn("getCodeByCodeNameAndType", code);
Quando eseguo il debug del test, getCodeByCodeNameAndType
restituisce null
, anziché il code
previsto. Cosa sto sbagliando?
Versione NUnit: 2.2.8
Soluzione
Un DynamicMock crea un nuovo oggetto in memoria che rappresenta l'interfaccia o la classe marshallable (eredita da MarshalByRef) che si desidera deridere.
Prova questo:
var _websiteConfigurationMock = new DynamicMock(typeof(WebSiteConfiguration));
_websiteConfigurationMock.ExpectAndReturn("getCodeByCodeNameAndType", code);
WebSiteConfiguration conf = (WebSiteConfiguration)_websiteConfigurationMock.MockInstance;
var x = conf.getCodeByCodeNameAndType("CATALOG_Brands_MinQty", item.Catalog);
Nota che la terza riga non funzionerà a meno che WebSiteConfiguration non erediti da MarshalByRef.
Quello che fai di solito è deridere un'interfaccia e ottenere un nuovo oggetto che implementa questa interfaccia, ma si comporta nel modo in cui l'hai configurato, senza dover andare e creare un tipo concreto per esso, quindi non lo sono del tutto sicuro che ciò che stai facendo funzionerà a meno che tu non utilizzi un migliore framework di isolamento, come TypeMock in grado di intercettare chiamate a metodi / proprietà statici in oggetti esistenti.
Altri suggerimenti
Mi dispiace, ma non ho mai usato NUnit.Mocks - ma ho una certa esperienza con NMock e Moq [che, a proposito, lo consiglio vivamente]. In genere, si utilizza una libreria beffarda per generare proxy per le definizioni di interfaccia e presumo che NUnit.Mocks funzioni allo stesso modo.
Pertanto, se desideri deridere il tuo singleton, probabilmente dovrai fare quanto segue,
a. Crea un'interfaccia, ad esempio
// All methods you would like to mock from this class, should
// be members of this interface
public interface IWebSiteConfiguration
{
// Should match signature of method you are mocking
CodeType getCodeByCodeNameAndType (
string codeString,
CatalogType catalogType);
}
b. & Quot; Implementare " Interfaccia
// You've already written the method, interface matches signature,
// should be as easy as slapping interface on class declaration
public class WebSiteConfiguration : IWebSiteConfiguration { }
c. Utilizza l'interfaccia
va bene, quindi passo c. è dove sarà la maggior parte del tuo lavoro. Logicamente, se stai deridendo il tuo singleton, stai effettivamente testando l'unità del consumatore [che hai lasciato fuori dal tuo campione]. Per c. è sufficiente aggiungere un parametro al ctor del consumatore o aggiungere una proprietà accessibile pubblicamente di tipo "IWebSiteConfiguration", quindi fare riferimento internamente al membro dell'istanza e richiamare i metodi su questa nuova interfaccia. Considera questo,
è stato
public class MyClass
{
public MyClass () { }
public void DoSomething ()
{
// bad singleton! bad boy! static references are bad! you
// can't change them! convenient but bad!
code = WebSiteConfiguration.Instance.getCodeByCodeNameAndType (
"some.string",
someCatalog)
}
}
diventa ??p>
public class MyClass
{
private readonly IWebSiteConfiguration _config = null;
// just so you don't break any other code, you can default
// to your static singleton on a default ctor
public MyClass () : this (WebSiteConfiguration.Instance) { }
// new constructor permits you to swap in any implementation
// including your mock!
public MyClass (IWebSiteConfiguration config)
{
_config = config;
}
public void DoSomething ()
{
// huzzah!
code = _config.getCodeByCodeNameAndType ("some.string", someCatalog)
}
}
Nel test unitario, creare la simulazione, passare un riferimento della simulazione al consumatore e testare il consumatore.
[Test]
public void Test ()
{
IWebSiteConfiguration mockConfig = null;
// setup mock instance and expectation via
// NUnit.Mocks, NMock, or Moq
MyClass myClass = new MyClass (mockConfig);
myClass.DoSomething ();
// verify results
}
Questo serve anche come introduzione pratica a Dependency Injection [DI]. È semplicemente la pratica di trasmettere o "iniettare" riferimenti di servizi [ad esempio la classe di configurazione del tuo sito Web] al consumatore, piuttosto che fare in modo che il consumatore invochi il servizio direttamente [ad esempio tramite una classe singleton statica].
Spero che questo aiuti :)
Sembra che ci sia una sorta di soluzione per questo usando la riflessione, o forse ho capito male.
È discusso qui: http: //www.geekbeing .com / 2010/05/23 / how-to-unit-test-Singleton-mod-in-c
Potrebbe davvero funzionare?
public class TestableSingleton : SingletonClass
{
public TestableSingleton ()
{
FieldInfo fieldInfo = typeof(SingletonClass)
.GetField("_instance",
BindingFlags.Static | BindingFlags.NonPublic);
fieldInfo.SetValue(Instance, this);
}
}
Progetto disponibile su https://github.com/rbabreu/TestableSingleton
In realtà non sono riuscito a compilarlo su Visual Studio poiché SingletonClass avrebbe un costruttore privato. Se qualcuno dovesse farlo funzionare, sarebbe bello evitare il sovraccarico del modello di adattatore.