การจำลองวัตถุด้วย Moq เมื่อตัวสร้างมีพารามิเตอร์


94

ฉันมีวัตถุที่พยายามจะล้อเลียนโดยใช้ moq ตัวสร้างของวัตถุมีพารามิเตอร์ที่ต้องการ:

public class CustomerSyncEngine {
    public CustomerSyncEngine(ILoggingProvider loggingProvider, 
                              ICrmProvider crmProvider, 
                              ICacheProvider cacheProvider) { ... }
}

ตอนนี้ฉันกำลังพยายามสร้างการจำลองสำหรับวัตถุนี้โดยใช้ไวยากรณ์ v3 "setup" ของ moq หรือ v4 "Mock.Of" แต่ไม่สามารถคิดออกได้ ... ทุกสิ่งที่ฉันพยายามไม่ได้รับการตรวจสอบ นี่คือสิ่งที่ฉันมีจนถึงตอนนี้ แต่บรรทัดสุดท้ายให้ฉันเป็นของจริงไม่ใช่ของจำลอง เหตุผลที่ฉันทำเช่นนี้เนื่องจากฉันมีวิธีการใน CustomerSyncEngine ที่ฉันต้องการตรวจสอบว่ากำลังถูกเรียก ...

// setup
var mockCrm = Mock.Of<ICrmProvider>(x => x.GetPickLists() == crmPickLists);
var mockCache = Mock.Of<ICacheProvider>(x => x.GetPickLists() == cachePickLists);
var mockLogger = Mock.Of<ILoggingProvider>();

// need to mock the following, not create a real class like this...
var syncEngine = new CustomerSyncEngine(mockLogger, mockCrm, mockCache);

คุณสามารถระบุวิธีตัวอย่างที่คุณต้องการตรวจสอบว่าถูกเรียกได้หรือไม่?
Ciaran

4
ดังนั้นถ้าฉันมีการพึ่งพาคลาสมากกว่าอินเทอร์เฟซฉันต้องล้อเลียนแม้กระทั่งการอ้างอิงของพวกเขาสิ่งนี้จะลดลงซ้ำ ๆ ในท้ายที่สุดฉันบังคับให้ใช้อินเทอร์เฟซบางอย่างเพื่อให้โค้ดของฉันสามารถทดสอบได้แม้ว่าฉันจะไม่ต้องการอินเทอร์เฟซในโค้ดก็ตาม ฉันคิดว่าอินเทอร์เฟซที่มากเกินไปเป็นกลิ่นที่ยิ่งใหญ่กว่าการเยาะเย้ยคลาสคอนกรีต ...
Tarion

คำตอบ:


34

บรรทัดสุดท้ายจะให้อินสแตนซ์จริงแก่คุณเนื่องจากคุณกำลังใช้คีย์เวิร์ดใหม่ไม่ใช่เยาะเย้ย CustomerSyncEngine

คุณควรใช้ Mock.Of<CustomerSyncEngine>()

ปัญหาเดียวของประเภท Mocking Concrete คือ Moq จะต้องมีตัวสร้างเริ่มต้นสาธารณะ (โดยไม่มีพารามิเตอร์) หรือคุณต้องสร้าง Moq ด้วยข้อกำหนดของตัวสร้างอาร์กิวเมนต์ http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

สิ่งที่ดีที่สุดที่จะทำคือคลิกขวาที่ชั้นเรียนของคุณแล้วเลือก Extract interface


3
ในส่วนที่เกี่ยวข้องกับปัญหาทางเลือกหนึ่งคือการใช้คอนเทนเนอร์ AutoMocking สิ่งที่ฉันชอบคือ Machine ปลอมร่วมกับ Machine ข้อมูลจำเพาะการใช้คอนเทนเนอร์อัตโนมัติทำให้ง่ายต่อการทดสอบพื้นที่ผิวที่เล็กกว่า สมมติว่าแอนดรูว์จำเป็นต้องทดสอบวิธีการCustomerSyncEngineที่ใช้เฉพาะICrmProviderกับการจำลองการใช้งานแบบดั้งเดิมเท่านั้นที่ต้องจัดเตรียมไว้สำหรับทั้ง 3 อินเทอร์เฟซในขณะที่คอนเทนเนอร์อัตโนมัติจะอนุญาตให้คุณใส่ได้เพียงอันเดียว
Chris Marisic

74

เปลี่ยนบรรทัดสุดท้ายเป็น

var syncEngine = new Mock<CustomerSyncEngine>(mockLogger, mockCrm, mockCache).Object;

และควรใช้งานได้


3
ไม่แน่ใจว่าความคิดเห็นนี้ใช้กับคำตอบของฉันอย่างไร
Suhas

2
เนื่องจากจะทำให้เกิดข้อผิดพลาดในการคอมไพล์เป็น mockLogger และคนอื่น ๆ จะโยนข้อยกเว้นว่าพวกเขาไม่มีคุณสมบัติ Object
Justin Pihony

2
เนื่องจาก OP ใช้ Mock.Of <T> () เพื่อสร้าง mocks ของประเภท logger, crm และ cache อ็อบเจ็กต์ที่ส่งคืนจะถูกส่งคืนเป็น T ไม่ใช่ Mock <T> ดังนั้นจึงไม่จำเป็นต้องใช้ mockLogger.Object และอื่น ๆ เมื่อมอบให้กับ Mock of CustomerSyncEngine และตามที่ @JustinPihony กล่าวถึงควรแสดงข้อผิดพลาดเวลาออกแบบ
Josh Gust

1
@suhas ไม่ควรเป็นของเขาnew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB

@GiriB ไม่จำเป็น แต่เป็นไปได้เนื่องจากการจำลองถูกกำหนดด้วย Params public Mock (วัตถุ params [] args)
JiříHerník
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.