Il problema fondamentale, a mio avviso, è che i risultati TMock<T>.Create
nella classe in prova (CUT) vengono istanziati. Sospetto che il framework sia stato progettato partendo dal presupposto che si sarebbe deriso una classe base astratta. In tal caso, l'istanziazione sarebbe benigna. Sospetto che tu abbia a che fare con un codice legacy che non ha una classe base astratta e comoda per il CUT. Ma nel tuo caso, l'unico modo per istanziare il CUT consiste nel passare i parametri al costruttore e quindi sconfiggere l'intero scopo del derisione. E immagino piuttosto che sarà molto impegnativo riprogettare il codice legacy finché non si avrà una classe base astratta per tutte le classi che devono essere prese in giro.
Si sta scrivendo TMock<TFoo>.Create
dove TFoo
è una classe. Ciò comporta la creazione di un oggetto proxy.Ciò accade nel TObjectProxy<T>.Create
. Il codice di cui si presenta così:
constructor TObjectProxy<T>.Create;
var
ctx : TRttiContext;
rType : TRttiType;
ctor : TRttiMethod;
instance : TValue;
begin
inherited;
ctx := TRttiContext.Create;
rType := ctx.GetType(TypeInfo(T));
if rType = nil then
raise EMockNoRTTIException.Create('No TypeInfo found for T');
ctor := rType.GetMethod('Create');
if ctor = nil then
raise EMockException.Create('Could not find constructor Create on type ' + rType.Name);
instance := ctor.Invoke(rType.AsInstance.MetaclassType, []);
FInstance := instance.AsType<T>();
FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType);
FVMInterceptor.Proxify(instance.AsObject);
FVMInterceptor.OnBefore := DoBefore;
end;
Come si può vedere il codice fa un presupposto che la classe ha un costruttore senza parametri. Quando si chiama questo sulla classe, il cui costruttore ha parametri, questo si traduce in un'eccezione RTTI di runtime.
Come ho capito il codice, la classe viene creata solo al fine di intercettare i suoi metodi virtuali. Non vogliamo fare nient'altro con la classe, dal momento che preferirebbe sconfiggere lo scopo di deriderlo. Tutto ciò di cui hai veramente bisogno è un'istanza di un oggetto con un vtable adatto che possa essere manipolato da TVirtualMethodInterceptor
. Non hai bisogno o vuoi che il tuo costruttore funzioni. Vuoi solo essere in grado di prendere in giro una classe che capita di avere un costruttore che ha parametri.
Quindi, invece di chiamare questo codice al costruttore, suggerisco di modificarlo per farlo chiamare NewInstance
. Questo è il minimo indispensabile che devi fare per avere un vtable che può essere manipolato. E dovrai anche modificare il codice in modo che non tenti di distruggere l'istanza di simulazione e invece chiama FreeInstance
. Tutto questo funzionerà bene fino a quando tutto ciò che fai è chiamare metodi virtuali sul mock.
Le modifiche appaiono così:
constructor TObjectProxy<T>.Create;
var
ctx : TRttiContext;
rType : TRttiType;
NewInstance : TRttiMethod;
instance : TValue;
begin
inherited;
ctx := TRttiContext.Create;
rType := ctx.GetType(TypeInfo(T));
if rType = nil then
raise EMockNoRTTIException.Create('No TypeInfo found for T');
NewInstance := rType.GetMethod('NewInstance');
if NewInstance = nil then
raise EMockException.Create('Could not find NewInstance method on type ' + rType.Name);
instance := NewInstance.Invoke(rType.AsInstance.MetaclassType, []);
FInstance := instance.AsType<T>();
FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType);
FVMInterceptor.Proxify(instance.AsObject);
FVMInterceptor.OnBefore := DoBefore;
end;
destructor TObjectProxy<T>.Destroy;
begin
TObject(Pointer(@FInstance)^).FreeInstance;//always dispose of the instance before the interceptor.
FVMInterceptor.Free;
inherited;
end;
Francamente questo sembra un po 'più ragionevole per me. Non ha sicuramente senso chiamare costruttori e distruttori.
Per favore fatemi sapere se sono largo del marchio qui e ho perso il punto. Questo è del tutto possibile!
Ecco cosa non capisco. Se stai cercando di deridere una classe, perché vuoi creare un'istanza della classe che stai deridendo. Sicuramente il punto di derisione è che tu, beh, prendi in giro la classe. –
Quando si esegue 'TMock .Create' il framework Mocks crea un'istanza di' TFoo'. Forse non capisco mock, ma ho pensato che il punto fosse che hai creato qualcosa che non era 'TFoo'. Voglio dire, se tutto ciò che devi fare è creare 'TFoo', quindi fallo. Se vuoi prenderlo in giro, trova un framework che crei una simulazione di 'TFoo' piuttosto che un'istanza di' TFoo'. –
@David. Mi dispiace, la mia domanda salta direttamente al mio problema senza alcun background; Hai ragione. Voglio prendere in giro una classe il cui costruttore ha un parametro (s). Poiché l'esempio fornito nel progetto Delphi-Mocks mostra [esempio TesTObjectMock] (https://github.com/VSoftTechnologies/Delphi-Mocks/blob/master/Sample1Main.pas) la classe sotto test (TFoo) viene passata come parametro generico come in mock: = TMock .create. Il problema è nella funzione di classe "Crea" e chiama "Invoke". –
TDF