Delphi XE2中不满意的依赖异常

时间:2013-10-30 23:17:47

标签: delphi unit-testing dependency-injection spring4d

我有以下界面:

unit uICodec;

interface
uses UITypes,uTPLb_CryptographicLibrary ;

type
  ICodec = interface
    ['{B1858F24-5B76-4468-8BD5-55684EA43CCD}']
    procedure EncryptString(const Plaintext: string; var CipherText_Base64:
      ansistring);
    procedure DecryptString(var Plaintext: string; const CipherText_Base64:
      ansistring);
    function RCGL:TCryptographicLibrary;
    procedure WCGL(const c:TCryptographicLibrary);
    property CryptoLibrary:TCryptographicLibrary read RCGL write WCGL;
    function RSCI:string;
    procedure WSCI(const c:string);
    property StreamCipherId:string read RSCI write WSCI;
    function RBCI:string;
    procedure WBCI(const c:string);
    property BlockCipherId:string read RBCI write WBCI;
    function RCMI:string;
    procedure WCMI(const c:string);
    property ChainModeId:string read RCMI write WCMI;
    function RPWD:string;
    procedure WPWD(const c:string);
    property Password:string read RPWD write WPWD;
  end;

与其相关的模拟类,其中函数/过程不执行任何操作:

unit uMockTPWDDBManager;

interface

uses classes,uTPLb_CryptographicLibrary,uICodec,UITypes;

type

  TMockCodec1 = class(TInterfacedObject,ICodec)
//    constructor Create (Aowner: TComponent);
    procedure EncryptString(const Plaintext: string; var CipherText_Base64:
      ansistring);
    procedure DecryptString(var Plaintext: string; const CipherText_Base64:
      ansistring);
  private
    function RCGL:TCryptographicLibrary;
    procedure WCGL(const c:TCryptographicLibrary);
    function RSCI:string;
    procedure WSCI(const c:string);
    function RBCI:string;
    procedure WBCI(const c:string);
    function RCMI:string;
    procedure WCMI(const c:string);
    function RPWD:string;
    procedure WPWD(const c:string);
  end;
....
function TMockCodec1.RBCI: string;
begin

end;
...
initialization
  GlobalContainer.RegisterComponent<TMockCodec1>.Implements<ICodec>;
end.

当我尝试使用以下代码运行测试时,出现错误

procedure TestTPWDDBManager.SetUp;
begin

GlobalContainer.Build;
  FCodec1:=ServiceLocator.GetService<ICodec>;//EUnsatisfiedDependencyException with message 'Unsatisfied dependency  for the service "ICodec"

我仔细检查过,ICodec中的所有程序/功能都与TMockCodec1中的相同。

我哪里错了?

[UPDATE]

我遵循大卫的建议,我想,我发现了这个错误。我的.dpr如下:

uses   DUnitTestRunner,   
uMockTPWDDBManager in 'uMockTPWDDBManager.pas',   
uTCodec1 in '..\uTCodec1;.pas';  

单元uMockTPWDDBManager具有以下初始化:

...
initialization
  GlobalContainer.RegisterComponent<TCodec1>.Implements<ICodec>;
end.

和单元uTCodec1具有以下初始化:

initialization
  GlobalContainer.RegisterComponent<TMockCodec1>.Implements<ICodec>;

当我从.dpr中删除uTCodec1时,错误消息消失了。

我认为同一个接口的两个初始化不能在同一个.dpr中共存。

我说错了吗?

[SSCCE]

不幸的是,我只能将单位数减少到3个。

以下是.dpr

program TPWDDBManager;
{

  Delphi DUnit Test Project
  -------------------------
  This project contains the DUnit test framework and the GUI/Console test runners.
  Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options
  to use the console test runner.  Otherwise the GUI test runner will be used by
  default.

}

{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}

uses
  DUnitTestRunner,
  TestuTPWDDBManager in 'TestuTPWDDBManager.pas',
  uTPWDDBManager in '..\uTPWDDBManager.pas';

{$R *.RES}

begin
  DUnitTestRunner.RunRegisteredTests;
end.

这是第一个单位:

unit uTPWDDBManager;

interface

uses IniFiles,uICodec,UITypes;

type
  ICodec = interface
['{B1858F24-5B76-4468-8BD5-55684EA43CCD}']
    procedure EncryptString(const Plaintext: string; var CipherText_Base64:
      ansistring);
    procedure DecryptString(var Plaintext: string; const CipherText_Base64:
      ansistring);
end;

  IInsertPassword = interface
    ['{B197F2EE-8C65-4E59-897F-F69E6E8D252F}']
    function ShowModal: Integer;
    function GetOldPWD : String;
    function GetNewPWD : String;
  end;

  IMessageDlg = interface
    ['{5A527174-50D4-4BB9-8E9F-6A9926B2893C}']
    function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons:
      TMsgDlgButtons;HelpCtx: Longint): Integer;
  end;

  IPWDDBManager = interface
    ['{51C993FE-D96A-4419-AB80-00D65E16C6F8}']
    function GetDBPWD (const Key : string; var Reset : Boolean): string;
    function ChangePWD (OldKey, NewKey : string): string;
    procedure Reset;
  end;

  TPWDDBManager=class(TIniFile,IPWDDBManager)
  private
    FRefCount: Integer;
  protected
  function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    constructor Create(const FileName: string; Fcodec: ICodec ;
      meggagedlg: IMessageDlg);virtual;
    function GetDBPWD (const Key: string; var Reset: Boolean): string;
    function ChangePWD (OldKey, NewKey : string): string;
    procedure Reset;
    property RefCount: Integer read FRefCount;
  end;

implementation

uses Dialogs, SysUtils,Spring.container;

{ TPWDDBManager }

function TPWDDBManager.ChangePWD (OldKey, NewKey : string): string;
begin
end;

constructor TPWDDBManager.Create(const FileName: string; Fcodec: ICodec ;
  meggagedlg: IMessageDlg);
begin
end;

function TPWDDBManager.GetDBPWD (const Key : string; var Reset : Boolean): string;
begin
  result:='';
end;

function TPWDDBManager.QueryInterface(const IID: TGUID; out Obj): Integer;
const
  E_NOINTERFACE = $80004002;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

procedure TPWDDBManager.Reset;
begin
end;

function TPWDDBManager._AddRef: Integer;
begin
end;

function TPWDDBManager._Release: Integer;
begin
end;

end.

这是第二个:

    unit TestuTPWDDBManager;
    {

      Delphi DUnit Test Case
      ----------------------
      This unit contains a skeleton test case class generated by the Test Case Wizard.
      Modify the generated code to correctly setup and call the methods from the unit
      being tested.

    }

        interface

        uses TestFramework,IniFiles,classes,UITypes,uTPWDDBManager;

        type

      tcodec1=class(tcodec)

      end;


          TMockCodec1 = class(TInterfacedObject,ICodec)
            procedure EncryptString(const Plaintext: string; var CipherText_Base64:
              ansistring);
            procedure DecryptString(var Plaintext: string; const CipherText_Base64:
              ansistring);
          end;

          TMockInsertPassword1 = class(TInterfacedObject,IInsertPassword )
            constructor create (Aowner: TComponent);
            function ShowModal: Integer;
            function GetOldPWD : String;
            function GetNewPWD : String;
          end;

          TMockMessagedlg = class(TInterfacedObject,IMessageDlg)
            function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons:
              TMsgDlgButtons;HelpCtx: Longint): Integer;
          end;

          // Test methods for class TPWDDBManager

          TestTPWDDBManager = class(TTestCase)
          strict private
            FCodec1: ICodec;
            FPWDDBManager: IPWDDBManager;
            Inifile:TInifile;
            mockmessagedlg:IMessageDlg;
            ReturnValue: string;
          public
            procedure SetUp; override;
            procedure TearDown; override;
          published
            procedure TestGetPWD;
          end;

        implementation

        uses SysUtils,Spring.container,Spring.Services;

        procedure TestTPWDDBManager.SetUp;
        begin
          GlobalContainer.Build;
          FCodec1:=ServiceLocator.GetService<ICodec>;
          mockmessagedlg:=ServiceLocator.GetService<IMessageDlg>;
          FPWDDBManager :=TPWDDBManager.Create(ChangeFileExt((ParamStr(0)),'.ini'),
            FCodec1,mockmessagedlg);
        end;

        procedure TestTPWDDBManager.TearDown;
        begin
          Inifile.Free;
        end;

        procedure TestTPWDDBManager.TestGetPWD;
        var
          reset: boolean;
        begin
        //// this instruction deletes a .ini file ///
          DeleteFile(ChangeFileExt((ParamStr(0)),'.ini'));
          ReturnValue := FPWDDBManager.GetDBPWD('a',reset);
          CheckEqualsString(ReturnValue,'');
          // TODO: Validate method results
        end;

        procedure TMockCodec1.DecryptString(var Plaintext: string;
          const CipherText_Base64: ansistring);
        begin
        Plaintext:=StringReplace(string(CipherText_Base64),'encripted','decripted',[
          rfReplaceAll,rfIgnoreCase]);
        end;

        procedure TMockCodec1.EncryptString(const Plaintext: string;
          var CipherText_Base64: ansistring);
        begin
          CipherText_Base64:=ansistring(StringReplace(Plaintext,'decripted','encripted',[
            rfReplaceAll,rfIgnoreCase]));
        end;

        { TMockInsertPassword1 }

        constructor TMockInsertPassword1.create(Aowner: TComponent);
        begin
        end;

        function TMockInsertPassword1.GetNewPWD: String;
        begin
          result:='new pwd decripted';
        end;

        function TMockInsertPassword1.GetOldPWD: String;
        begin
          result:='old pwd encripted';
        end;

        function TMockInsertPassword1.ShowModal: Integer;
        begin
          result:=1;
        end;

        { TMockMessagedlg }

        function TMockMessagedlg.MessageDlg(const Msg: string; DlgType: TMsgDlgType;
          Buttons: TMsgDlgButtons; HelpCtx: Integer): Integer;
        begin
          result:=-1;
          if Msg='File ' +
            'E:\Delphi\Projects\Components\Tests\TPWDDBManager\TestFolder\Win32\Debug\.ini doesn''t exists' then
            result:=1
          else if Msg='File ' +
          'E:\Delphi\Projects\Components\Tests\TPWDDBManager\TestFolder\Win32\Debug\.ini doesn''t exists' then
            result:=2
        end;

        initialization
          // Register any test cases with the test runner
          RegisterTest(TestTPWDDBManager.Suite);
          GlobalContainer.RegisterComponent<TMockCodec1>.Implements<ICodec>;
          GlobalContainer.RegisterComponent<tcodec1>.Implements<ICodec>;
GlobalContainer.RegisterComponent<TMockInsertPassword1>.Implements<IInsertPassword>;
          GlobalContainer.RegisterComponent<TMockMessagedlg>.Implements<IMessageDlg>;
        end.

我可以编译源代码,但是当我运行测试时,我收到错误消息EUnsatisfiedDependencyException,并显示消息“对服务的不满意依赖”ICodec“

如果我删除了类tcodec1并删除了GlobalContainer.RegisterComponent<tcodec1>.Implements<ICodec>; 然后没有更多错误消息。

是因为你不能在同一个项目中注册多个类并引用相同的接口吗?

1 个答案:

答案 0 :(得分:0)

如果您对同一服务类型进行多次注册,则应使用不同的名称注册这些注册。否则,如果您调用Resolve / GetService,容器将无法决定返回哪一个。

我想为单元测试提供的另一个建议是:不要在那里使用容器,而是手动连接所需的依赖项(即模拟)。在你的例子中它甚至没有意义,因为你没有让容器构建你正在测试的系统(所以它可以注入你正在嘲笑的任何依赖)。

如果您使用中继中的最新版本,则在注册多个未命名服务时不应再出现错误。但是,它将返回服务的最后一个注册组件(或者您调用的最后一个组件.AsDefault on)。这种情况最有可能发生变化,因此我的第一个建议仍然存在(如果您有多个,则使用命名注册)。