การเริ่มต้นวัตถุจำลอง - MockIto


122

มีหลายวิธีในการเริ่มต้นวัตถุจำลองโดยใช้ MockIto วิธีใดดีที่สุดในบรรดาสิ่งเหล่านี้

1

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2

@RunWith(MockitoJUnitRunner.class)

[แก้ไข] 3.

mock(XXX.class);

แนะนำหน่อยว่ามีวิธีอื่นที่ดีกว่านี้ไหม ...

คำตอบ:


153

สำหรับการเริ่มต้นล้อเลียนโดยใช้ตัววิ่งหรือMockitoAnnotations.initMocksวิธีแก้ปัญหาที่เทียบเท่ากันอย่างเคร่งครัด จาก javadoc ของMockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


วิธีแก้ปัญหาแรก (พร้อมด้วยMockitoAnnotations.initMocks) สามารถใช้ได้เมื่อคุณกำหนดค่านักวิ่งเฉพาะ ( SpringJUnit4ClassRunnerตัวอย่าง) ไว้แล้วในกรณีทดสอบของคุณ

วิธีที่สอง (พร้อมด้วยMockitoJUnitRunner) เป็นวิธีที่คลาสสิกและเป็นที่ชื่นชอบของฉัน รหัสง่ายกว่า การใช้นักวิ่งให้ประโยชน์อย่างมากในการตรวจสอบการใช้เฟรมเวิร์กโดยอัตโนมัติ (อธิบายโดย@David Wallaceในคำตอบนี้ )

ทั้งสองวิธีนี้อนุญาตให้แบ่งปันการล้อเลียน (และสายลับ) ระหว่างวิธีการทดสอบ นอกจาก@InjectMocksนี้ยังอนุญาตให้เขียนการทดสอบหน่วยได้อย่างรวดเร็ว รหัสจำลองแบบสำเร็จรูปลดลงการทดสอบอ่านง่ายขึ้น ตัวอย่างเช่น:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

ข้อดี: โค้ดมีน้อย

จุดด้อย: มนต์ดำ IMO ส่วนใหญ่เกิดจากคำอธิบายประกอบ @InjectMocks ด้วยคำอธิบายประกอบนี้ "คุณหลวมความเจ็บปวดของรหัส" (ดูความคิดเห็นที่ยอดเยี่ยมของ@Brice )


วิธีที่สามคือสร้างการจำลองของคุณในแต่ละวิธีการทดสอบ อนุญาตตามที่@mlkอธิบายไว้ในคำตอบว่ามี " การทดสอบในตัวเอง "

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

ข้อดี: คุณแสดงให้เห็นอย่างชัดเจนว่า api ของคุณทำงานอย่างไร (BDD ... )

จุดด้อย: มีรหัสสำเร็จรูปมากกว่า (การสร้างล้อเลียน)


คำแนะนำของฉันคือการประนีประนอม ใช้@Mockคำอธิบายประกอบกับ@RunWith(MockitoJUnitRunner.class)แต่อย่าใช้@InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

ข้อดี: คุณแสดงให้เห็นอย่างชัดเจนว่า API ของคุณทำงานอย่างไร (การArticleManagerสร้างอินสแตนซ์ของฉันเป็นอย่างไร) ไม่มีรหัสสำเร็จรูป

จุดด้อย: การทดสอบไม่มีอยู่ในตัวความเจ็บปวดของรหัสน้อยลง


โปรดระวังคำอธิบายประกอบมีประโยชน์ แต่ไม่ได้ป้องกันคุณจากการออกแบบ OO ที่ไม่ดี (หรือทำให้เสื่อมเสีย) โดยส่วนตัวแล้วในขณะที่ฉันยินดีที่จะลดรหัสสำเร็จรูป แต่ฉันก็คลายความเจ็บปวดของรหัส (หรือ PITA) ซึ่งเป็นตัวกระตุ้นให้เปลี่ยนการออกแบบให้ดีขึ้นดังนั้นฉันและทีมงานจึงให้ความสำคัญกับการออกแบบ OO ฉันรู้สึกว่าการปฏิบัติตามการออกแบบ OO ด้วยหลักการเช่นการออกแบบ SOLID หรือแนวคิด GOOS นั้นสำคัญกว่ามากในการเลือกวิธีสร้างตัวอย่างล้อเลียน
Brice

1
(ติดตามผล) หากคุณไม่เห็นว่าวัตถุนี้สร้างขึ้นอย่างไรคุณจะไม่รู้สึกเจ็บปวดกับมันและโปรแกรมเมอร์ในอนาคตอาจตอบสนองได้ไม่ดีหากควรเพิ่มฟังก์ชันใหม่ อย่างไรก็ตามที่สามารถโต้แย้งได้ทั้งสองวิธีฉันแค่บอกว่าให้ระวังเรื่องนี้
Brice

6
ไม่ถูกต้องว่าสองสิ่งนี้เทียบเท่ากัน MockitoJUnitRunnerมันไม่เป็นความจริงว่ารหัสที่เรียบง่ายเป็นประโยชน์เฉพาะกับการใช้ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างโปรดดูคำถามที่stackoverflow.com/questions/10806345/…และคำตอบของฉัน
Dawood ibn Kareem

2
@Gontard ใช่แน่ใจว่าการอ้างอิงสามารถมองเห็นได้ แต่ฉันเห็นรหัสผิดพลาดเมื่อใช้วิธีนี้ เกี่ยวกับการใช้Collaborator collab = mock(Collaborator.class)ในความคิดของวิธีนี้แน่นอนเป็นวิธีการที่ถูกต้อง แม้ว่าสิ่งนี้อาจจะดูละเอียด แต่คุณสามารถเข้าใจและสามารถปรับสภาพใหม่ของการทดสอบได้ ทั้งสองวิธีมีข้อดีข้อเสียฉันยังไม่ได้ตัดสินใจว่าแนวทางใดดีกว่า Amyway เป็นไปได้เสมอที่จะเขียนอึและอาจขึ้นอยู่กับบริบทและ coder
Brice

1
@mlk ฉันเห็นด้วยกับคุณโดยสิ้นเชิง ภาษาอังกฤษของฉันไม่ดีมากและขาดความแตกต่าง ประเด็นของฉันคือการยืนยันในคำ UNIT
อน

30

ขณะนี้มี ( ณ v1.10.7) เป็นวิธีที่สี่เพื่อ mocks instantiate ซึ่งใช้ JUnit4 กฎที่เรียกว่าMockitoRule

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit ค้นหาคลาสย่อยของ TestRule ที่มีคำอธิบายประกอบด้วย @Ruleและใช้เพื่อตัดคำสั่งทดสอบที่ Runner จัดเตรียมไว้ให้ ข้อดีของสิ่งนี้คือคุณสามารถแยกเมธอด @Before, @After และแม้แต่ลอง ... จับ wrappers เป็นกฎ คุณสามารถโต้ตอบกับสิ่งเหล่านี้ได้จากภายในการทดสอบของคุณเช่นเดียวกับที่คาดการณ์ไว้

MockitoRule ทำงานเกือบเหมือนกับ MockitoJUnitRunnerยกเว้นว่าคุณสามารถใช้นักวิ่งอื่น ๆ เช่นParameterized (ซึ่งช่วยให้ตัวสร้างการทดสอบของคุณรับข้อโต้แย้งเพื่อให้การทดสอบของคุณทำงานได้หลายครั้ง) หรือตัวทดสอบของ Robolectric (ตัวโหลดคลาสจึงสามารถให้การแทนที่ Java ได้ สำหรับคลาสเนทีฟของ Android) สิ่งนี้ทำให้การใช้งาน JUnit และ Mockito เวอร์ชันล่าสุดมีความยืดหยุ่นมากขึ้นอย่างเคร่งครัด

สรุป:

  • Mockito.mock(): การเรียกใช้โดยตรงโดยไม่รองรับคำอธิบายประกอบหรือการตรวจสอบการใช้งาน
  • MockitoAnnotations.initMocks(this): การสนับสนุนคำอธิบายประกอบไม่มีการตรวจสอบการใช้งาน
  • MockitoJUnitRunner: การสนับสนุนคำอธิบายประกอบและการตรวจสอบการใช้งาน แต่คุณต้องใช้ตัววิ่งนั้น
  • MockitoRule: การสนับสนุนคำอธิบายประกอบและการตรวจสอบการใช้งานกับนักวิ่ง JUnit ใด ๆ

ดูเพิ่มเติม: JUnit @Rule ทำงานอย่างไร


3
ใน Kotlin กฎจะมีลักษณะดังนี้@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan

10

มีวิธีการทำเช่นนี้อย่างประณีต

  • หากเป็นการทดสอบหน่วยคุณสามารถทำได้:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • แก้ไข: หากเป็นการทดสอบการผสานรวมคุณสามารถทำได้ (ไม่ได้ตั้งใจให้ใช้กับ Spring เพียงแค่แสดงให้เห็นว่าคุณสามารถเริ่มต้น mocks ด้วย Runners ที่แตกต่างกัน):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }

1
หาก MOCK มีส่วนร่วมในการทดสอบ Integration ด้วยจะสมเหตุสมผลหรือไม่?
VinayVeluri

2
จริงๆแล้วมันจะไม่เป็นสิทธิของคุณ ฉันแค่อยากจะแสดงให้เห็นถึงความเป็นไปได้ของ Mockito ตัวอย่างเช่นหากคุณใช้ RESTFuse คุณต้องใช้นักวิ่งเพื่อให้คุณสามารถเริ่มต้น mocks ด้วย MockitoAnnotations.initMocks (this);
emd

8

Mockito คำอธิบายประกอบและนักวิ่งได้รับการกล่าวถึงอย่างดีข้างต้นดังนั้นฉันจะทุ่มให้กับคนที่ไม่มีใครรัก:

XXX mockedXxx = mock(XXX.class);

ฉันใช้สิ่งนี้เพราะฉันพบว่ามันมีคำอธิบายมากกว่าเล็กน้อยและฉันชอบการทดสอบหน่วย (ไม่ใช่การห้ามที่ถูกต้อง) ที่จะไม่ใช้ตัวแปรสมาชิกเพราะฉันต้องการให้การทดสอบของฉันมี (มากที่สุดเท่าที่จะทำได้) ในตัวเอง


มีข้อได้เปรียบอื่น ๆ นอกเหนือจากการใช้จำลอง (XX.class) ยกเว้นการทำให้กรณีทดสอบมีอยู่ในตัวเองหรือไม่?
VinayVeluri

ไม่เท่าที่ฉันรู้
Michael Lloyd Lee mlk

3
เวทมนตร์น้อยที่จะต้องทำความเข้าใจเพื่ออ่านแบบทดสอบ คุณประกาศตัวแปรและให้ค่า - ไม่มีคำอธิบายประกอบภาพสะท้อน ฯลฯ
Karu

2

ตัวอย่างเล็กน้อยสำหรับ JUnit 5 Jupiter "RunWith" ถูกลบออกตอนนี้คุณต้องใช้ Extensions โดยใช้ Annotation "@ExtendWith"

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

1

คำตอบอื่น ๆ นั้นยอดเยี่ยมและมีรายละเอียดเพิ่มเติมหากคุณต้องการ / ต้องการ
นอกจากนั้นฉันต้องการเพิ่ม TL; DR:

  1. ชอบใช้
    • @RunWith(MockitoJUnitRunner.class)
  2. หากคุณไม่สามารถ (เนื่องจากคุณใช้นักวิ่งคนอื่นอยู่แล้ว) ให้เลือกใช้
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. คล้ายกับ (2) แต่คุณไม่ควรใช้สิ่งนี้อีกต่อไป:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. หากคุณต้องการใช้การเยาะเย้ยในการทดสอบเพียงรายการเดียวและไม่ต้องการเปิดเผยต่อการทดสอบอื่น ๆ ในชั้นทดสอบเดียวกันให้ใช้
    • X x = mock(X.class)

(1) และ (2) และ (3) เป็นเอกสิทธิ์เฉพาะบุคคล
(4) สามารถใช้ร่วมกับอุปกรณ์อื่น ๆ ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.