2011-10-05 27 views
41

Tôi có một lớp mà tôi đang thử nghiệm với tư cách là một lớp phụ thuộc một lớp khác (một thể hiện được chuyển đến phương thức init của CUT). Tôi muốn giả lập lớp này bằng thư viện Python Mock.Đối tượng Python Mock với phương thức được gọi là nhiều lần

gì tôi có là cái gì đó như:

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.return_value = "the value I want the mock to return" 
assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return") 

cutobj = ClassUnderTest(mockobj) 

nào là tốt, nhưng "methodfromdepclass" là một phương pháp tham số, và như vậy tôi muốn tạo một đối tượng giả duy nhất mà phụ thuộc vào những gì đối số được truyền cho methodfromdepclass nó trả về các giá trị khác nhau.

Lý do tôi muốn hành vi tham số này là tôi muốn tạo nhiều phiên bản ClassUnderTest chứa các giá trị khác nhau (giá trị được tạo ra bởi giá trị được trả về từ mockobj).

Kinda những gì tôi đang suy nghĩ (điều này tất nhiên không hoạt động):

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42" 
mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99" 

assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42") 
assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99") 

cutinst1 = ClassUnderTest(mockobj, 42) 
cutinst2 = ClassUnderTest(mockobj, 99) 

# now cutinst1 & cutinst2 contain different values 

Làm thế nào để đạt được điều này "ifcalledwith" loại ngữ nghĩa?

Trả lời

60

Hãy thử side_effect

def my_side_effect(*args, **kwargs): 
    if args[0] == 42: 
     return "Called with 42" 
    elif args[0] == 43: 
     return "Called with 43" 
    elif kwarg['foo'] == 7: 
     return "Foo is seven" 

mockobj.mockmethod.side_effect = my_side_effect 
+4

Tuyệt vời, chính xác những gì tôi cần. Không phải là một fan hâm mộ của cú pháp, nhưng hoạt động tuyệt vời. Cảm ơn! –

+1

Một báo trước mà tôi muốn thêm ở đây vào đó tôi chạy khi sử dụng giải pháp này, hoạt động rất tốt, btw, là nếu bạn có ngoại lệ như tác dụng phụ, người ta phải nâng cao chúng, thay vì trả lại chúng. Thư viện Mock là tốt về việc cho phép bạn chỉ định một ngoại lệ cho side_effect và tìm ra nó, nhưng với phương pháp này bạn phải DIY. – ThatsAMorais

8

Tôi đã chạy vào này khi tôi đã làm thử nghiệm của riêng tôi. Nếu bạn không quan tâm đến chụp các cuộc gọi đến methodfromdepclass của bạn() nhưng chỉ cần nó để trở về một cái gì đó, sau đó những điều sau đây có thể đủ:

def makeFakeMethod(mapping={}): 
    def fakeMethod(inputParam): 
     return mapping[inputParam] if inputParam in mapping else MagicMock() 
    return fakeMethod 

mapping = {42:"Called with 42", 59:"Called with 59"} 
mockobj.methodfromdepclass = makeFakeMethod(mapping) 

Dưới đây là một phiên bản tham số:

def makeFakeMethod(): 
    def fakeMethod(param): 
     return "Called with " + str(param) 
    return fakeMethod 
+1

+1: cú pháp chặt hơn mẫu if-block với 'side_effect', có thể trả về Mock() thay vì không có để hành vi mặc định được duy trì? –

+0

Cảm ơn bạn đã đề xuất. Thay vào đó, hãy cập nhật nó để trả lại MagicMock(). – Addison

39

Một ngọt ngào chút :

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x] 

hoặc cho nhiều đối số:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x] 

hoặc với một giá trị mặc định:

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000) 

hoặc kết hợp cả hai:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000) 

và vui vẻ trên cao, chúng tôi đi.

+0

Rất ít, nhưng tôi thích nó. Điều gì sẽ xảy ra nếu 'lambda' trả về một cá thể' Mock' để các hành vi chưa được giải mã hoàn nguyên về hành vi thông thường của thư viện 'mock'? –

Các vấn đề liên quan