2012-06-04 24 views
20

Trong RhinoMocks, bạn chỉ có thể nói cho mocks của bạn để IgnoreArguments như một tuyên bố chăn. Trong Moq, có vẻ như, bạn phải chỉ định It.IsAny() cho mỗi đối số. Tuy nhiên, điều này không làm việc cho các đối số ref và out. Làm thế nào tôi có thể kiểm tra các phương pháp sau đây mà tôi cần phải MOQ cuộc gọi dịch vụ nội bộ để trả về một kết quả cụ thể:Làm thế nào để làm cho Moq bỏ qua đối số được ref hoặc ra

public void MyMethod() { 
    // DoStuff 

    IList<SomeObject> errors = new List<SomeObject>(); 
    var result = _service.DoSomething(ref errors, ref param1, param2); 

    // Do more stuff 
} 

Phương pháp thử:

public void TestOfMyMethod() { 
    // Setup 
    var moqService = new Mock<IMyService>(); 
    IList<String> errors; 
    var model = new MyModel(); 

    // This returns null, presumably becuase "errors" 
    // here does not refer to the same object as "errors" in MyMethod 
    moqService.Setup(t => t.DoSomething(ref errors, ref model, It.IsAny<SomeType>()). 
     Returns(new OtherType())); 
} 

UPDATE: Vì vậy, thay đổi lỗi từ "ref" để "out" hoạt động. Vì vậy, có vẻ như vấn đề thực sự là có một tham số ref mà bạn không thể tiêm.

+0

Bạn có thể gửi chữ ký của 'DoSomething' bởi vì trong mẫu của bạn nó có 3 đối số 'lỗi ra, param1, param2' nhưng trong thử nghiệm của bạn bạn đang gọi nó với hai đối số' lỗi ra, It.IsAny () 'Có thể bạn đang chế nhạo một tình trạng quá tải sai, vì mã của bạn nên hoạt động, hãy xem [moq phần phương thức trợ giúp] (http://code.google.com/p/moq/wiki/QuickStart). – nemesv

+0

Đó chỉ là một ví dụ - nhưng tôi đã cập nhật thử nghiệm song song với mã ví dụ – sydneyos

Trả lời

14

Như bạn đã tìm ra vấn đề là với đối số ref của bạn.

Moq hiện chỉ hỗ trợ khớp chính xác cho các đối số ref, có nghĩa là cuộc gọi chỉ phù hợp nếu bạn chuyển cùng một trường hợp bạn đã sử dụng trong Setup. Vì vậy, không có kết hợp chung vì vậy It.IsAny() sẽ không hoạt động.

Xem Moq quickstart

// ref arguments 
var instance = new Bar(); 
// Only matches if the ref argument to the invocation is the same instance 
mock.Setup(foo => foo.Submit(ref instance)).Returns(true); 

Và Moq discussion group:

Ref phù hợp có nghĩa là thiết lập là lần xuất hiện chỉ khi phương pháp này là gọi với cùng ví dụ. It.IsAny trả về null, vì vậy có lẽ không phải những gì bạn đang tìm kiếm.

Sử dụng cùng một phiên bản trong thiết lập với tư cách là người trong cuộc gọi thực tế và thiết lập sẽ khớp.

+1

Vì vậy, trong trường hợp có các cuộc gọi lồng nhau sao cho biến ref/out được đặt bên trong phương thức thực hiện cuộc gọi mà tôi đang cố gắng giả, tôi bị mắc kẹt. Bất kỳ đề xuất cho một khuôn khổ mocking mà không có giới hạn này? – sydneyos

+0

Tôi nghĩ rằng RhinoMocks IgnoreArguments() tùy chọn nên làm điều đó - sẽ cố gắng đó. – sydneyos

+0

Cập nhật, trong những trường hợp này, chúng tôi phải chuyển sang RhinoMocks. Việc thực hiện như sau: Lỗi IList ; _repository.Stub (t => t.MethodName (lỗi ngoài) .OutRef (danh sách mới ()). IgnoreArguments(); – sydneyos

2

Như @nemesv đã đề cập trước đây, It.IsAny trả về null để bạn không thể sử dụng nó làm tham số ref. Để cuộc gọi hoạt động, một đối tượng thực sự cần phải được truyền cho nó.

Sự cố xảy ra khi bạn không có quyền truy cập vào việc tạo đối tượng bạn muốn chuyển qua ref. Nếu bạn DID có quyền truy cập vào đối tượng thực sự, bạn có thể chỉ cần sử dụng nó trong bài kiểm tra của bạn, và quên đi cố gắng để thử nó ở tất cả.

Đây là giải pháp thay thế bằng cách sử dụng kỹ thuật Trích xuất và ghi đè sẽ cho phép bạn thực hiện điều đó. Như tên của nó, bạn giải nén bit có vấn đề mã vào phương thức riêng của nó. Sau đó, bạn ghi đè lên phương thức trong một lớp thử nghiệm kế thừa từ lớp đang được kiểm tra. Cuối cùng, bạn thiết lập đối tượng thực sự của mình, chuyển nó vào lớp thử nghiệm mới tạo của bạn và kiểm tra các cuộc gọi ref của bạn theo ý bạn.

Đây là đoạn mã dài (có nhiều mã), nhưng nó hiển thị trước và sau, với thử nghiệm vượt qua ở cuối.

using System; 
using System.Collections.Generic; 
using Moq; 
using MoqRefProblem; 
using NUnit.Framework; 

namespace MoqRefProblem 
{ 
    //This class is the one we want to have passed by ref. 
    public class FileContext 
    { 
     public int LinesProcessed { get; set; } 
     public decimal AmountProcessed { get; set; } 
    } 

    public interface IRecordParser 
    { 
     //The ref parameter below is what's creating the testing problem. 
     void ParseLine(decimal amount, ref FileContext context); 
    } 

    //This is problematic because we don't have a 
    //seam that allows us to set the FileContext. 
    public class OriginalFileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public OriginalFileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //This is the problem 
      var context = new FileContext(); 
      ParseItems(items, ref context); 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    } 

    //This class has had the creation of the FileContext extracted into a virtual 
    //method. 
    public class FileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public FileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //Instead of newing up a context, we'll get it from a virtual method 
      //that we'll override in a test class. 
      var context = GetFileContext(); 
      ParseItems(items, ref context); 
     } 

     //This is our extensibility point 
     protected virtual FileContext GetFileContext() 
     { 
      var context = new FileContext(); 
      return context; 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    //Create a test class that inherits from the Class under Test  
    //We will set the FileContext object to the value we want to 
    //use. Then we override the GetContext call in the base class 
    //to return the fileContext object we just set up. 
    public class MakeTestableParser : FileParser 
    { 
     public MakeTestableParser(IRecordParser recordParser) 
      : base(recordParser) 
     { 
     } 

     private FileContext _context; 

     public void SetFileContext(FileContext context) 
     { 
      _context = context; 
     } 

     protected override FileContext GetFileContext() 
     { 
      if (_context == null) 
      { 
       throw new Exception("You must set the context before it can be used."); 
      } 

      return _context; 
     } 
    } 

[TestFixture] 
public class WorkingFileParserTest 
{ 
    [Test] 
    public void ThisWillWork() 
    { 
     //Arrange 
     var recordParser = new Mock<IRecordParser>(); 

     //Note that we are an instance of the TestableParser and not the original one. 
     var sut = new MakeTestableParser(recordParser.Object); 
     var context = new FileContext(); 
     sut.SetFileContext(context); 

     var items = new List<decimal>() 
      { 
       10.00m, 
       11.50m, 
       12.25m, 
       14.00m 
      }; 

     //Act 
     sut.ParseFile(items); 

     //Assert 
     recordParser.Verify(x => x.ParseLine(It.IsAny<decimal>(), ref context), Times.Exactly(items.Count)); 
    } 
} 
Các vấn đề liên quan