Mockito: วิธีการตรวจสอบวิธีการถูกเรียกบนวัตถุที่สร้างขึ้นภายในวิธี?


322

ฉันใหม่กับ Mockito

จากคลาสด้านล่างฉันจะใช้ Mockito เพื่อตรวจสอบว่าsomeMethodถูกเรียกใช้หลังจากfooถูกเรียกได้อย่างไร

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}

ฉันต้องการโทรยืนยันดังต่อไปนี้

verify(bar, times(1)).someMethod();

ที่เป็นเช่นล้อเลียนของbarBar


2
stackoverflow.com/questions/6520242/… - แต่ฉันไม่ต้องการใช้ PowerMock
MRE

เปลี่ยน API หรือ PowerMock หนึ่งในสอง
John B

วิธีครอบคลุมบางสิ่งเช่นนี้ ?? โมฆะซิงโครไนซ์สาธารณะเริ่มต้น (BundleContext bundleContext) พ่นข้อยกเว้น {BundleContext bc = bundleContext; logger.info ("การเริ่มต้นรวมบริการ HTTP"); this.tracker = ServiceTracker ใหม่ (bc, HttpService.class.getName (), null) {@Override วัตถุสาธารณะ addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef) registerServlets (); ส่งคืน httpService; }}}
ShAkKiR

คำตอบ:


365

ฉีดพึ่งพา

หากคุณฉีดอินสแตนซ์บาร์หรือโรงงานที่ใช้สำหรับสร้างอินสแตนซ์บาร์ (หรือหนึ่งใน 483 วิธีการอื่นในการทำเช่นนี้) คุณจะต้องมีการเข้าถึงที่จำเป็นเพื่อทำการทดสอบ

ตัวอย่างโรงงาน:

ให้คลาส Foo เขียนดังนี้:

public class Foo {
  private BarFactory barFactory;

  public Foo(BarFactory factory) {
    this.barFactory = factory;
  }

  public void foo() {
    Bar bar = this.barFactory.createBar();
    bar.someMethod();
  }
}

ในวิธีการทดสอบของคุณคุณสามารถฉีด BarFactory เช่นนี้:

@Test
public void testDoFoo() {
  Bar bar = mock(Bar.class);
  BarFactory myFactory = new BarFactory() {
    public Bar createBar() { return bar;}
  };

  Foo foo = new Foo(myFactory);
  foo.foo();

  verify(bar, times(1)).someMethod();
}

โบนัส: นี่เป็นตัวอย่างของวิธีที่ TDD สามารถผลักดันการออกแบบรหัสของคุณ


6
มีวิธีการทำเช่นนี้โดยไม่ต้องแก้ไขคลาสสำหรับการทดสอบหน่วยหรือไม่
MRE

6
Bar bar = mock(Bar.class)แทนBar bar = new Bar();
John B

7
ไม่ใช่ที่ฉันรู้ แต่ฉันไม่แนะนำให้คุณปรับเปลี่ยนคลาสสำหรับการทดสอบหน่วย นี่เป็นการสนทนาเกี่ยวกับรหัสที่สะอาดและ SRP หรือ .. มันเป็นความรับผิดชอบของ method foo () ใน class Foo เพื่อสร้างวัตถุ Bar หากคำตอบคือใช่แสดงว่าเป็นรายละเอียดการใช้งานและคุณไม่ควรกังวลเกี่ยวกับการทดสอบการโต้ตอบโดยเฉพาะ (ดูคำตอบของ @ Michael) หากคำตอบคือไม่คุณกำลังแก้ไขชั้นเรียนเพราะความยากลำบากในการทดสอบของคุณคือธงสีแดงที่การออกแบบของคุณต้องการการปรับปรุงเล็ก ๆ น้อย ๆ
csturtz

3
คุณสามารถส่งวัตถุ "ของจริง" ไปที่ "ยืนยัน" ของ Mockito ได้หรือไม่
John B

4
นอกจากนี้คุณยังสามารถจำลองโรงงาน: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

คำตอบแบบคลาสสิกคือ "คุณทำไม่ได้" คุณทดสอบ API สาธารณะFooไม่ใช่ภายใน

มีพฤติกรรมของFooวัตถุ (หรือน้อยกว่าที่ดีบางอย่างวัตถุอื่น ๆ ในสภาพแวดล้อม) ที่ได้รับผลกระทบจากfoo()? ถ้าเป็นเช่นนั้นทดสอบว่า และถ้าไม่วิธีการทำอะไร?


4
แล้วคุณจะทดสอบอะไรที่นี่จริง ๆ ? ประชาชน API ของFooมีpublic void foo()ที่ internals ที่มีเพียงบาร์ที่เกี่ยวข้อง
behelit

15
การทดสอบเฉพาะ API สาธารณะก็ถือว่าใช้ได้จนกว่าจะมีข้อบกพร่องของแท้ที่มีผลข้างเคียงที่ต้องทำการทดสอบ ตัวอย่างเช่นการตรวจสอบว่าวิธีส่วนตัวกำลังปิดการเชื่อมต่อ HTTP อย่างถูกต้องหรือไม่จนกว่าคุณจะค้นพบว่าวิธีส่วนตัวไม่ได้ปิดการเชื่อมต่ออย่างถูกต้องและทำให้เกิดปัญหาใหญ่ ณ จุดนั้น Mockito และverify()เป็นประโยชน์อย่างมากแม้ว่าคุณจะไม่ได้นมัสการที่แท่นบูชาศักดิ์สิทธิ์แห่งการทดสอบการรวมกลุ่มอีกต่อไป
Dawngerpony

@DuffJ ฉันไม่ได้ใช้ Java แต่ดูเหมือนว่าสิ่งที่คอมไพเลอร์หรือเครื่องมือวิเคราะห์รหัสของคุณควรตรวจพบ
user247702

3
ฉันเห็นด้วยกับ DuffJ ในขณะที่ฟังก์ชั่นการเขียนโปรแกรมเป็นเรื่องสนุกมีจุดที่โค้ดของคุณโต้ตอบกับโลกภายนอก ไม่สำคัญว่าคุณจะเรียกมันว่า "ภายใน", "ผลข้างเคียง" หรือ "ฟังก์ชั่น" คุณต้องการทดสอบการโต้ตอบนั้น: ถ้ามันเกิดขึ้นและถ้ามันเกิดขึ้นจำนวนครั้งที่ถูกต้องและมีข้อโต้แย้งที่ถูกต้อง @Stijn: มันอาจเป็นตัวอย่างที่ไม่ดี (แต่ถ้ามีการเชื่อมต่อหลายรายการควรเปิดและมีเพียงบางการเชื่อมต่อเท่านั้นที่น่าสนใจ) ตัวอย่างที่ดีกว่าคือการตรวจสอบสภาพอากาศข้อมูลที่ถูกต้องจะถูกส่งผ่านการเชื่อมต่อ
Andras Balázs Lajtha

13

หากคุณไม่ต้องการใช้ DI หรือโรงงาน คุณสามารถปรับโครงสร้างห้องเรียนของคุณในแบบที่ยุ่งยากเล็กน้อย:

public class Foo {
    private Bar bar;

    public void foo(Bar bar){
        this.bar = (bar != null) ? bar : new Bar();
        bar.someMethod();
        this.bar = null;  // for simulating local scope
    }
}

และชั้นทดสอบของคุณ:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock Bar barMock;
    Foo foo;

    @Test
    public void testFoo() {
       foo = new Foo();
       foo.foo(barMock);
       verify(barMock, times(1)).someMethod();
    }
}

คลาสที่เรียกเมธอด foo ของคุณจะทำดังนี้:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}

อย่างที่คุณเห็นเมื่อเรียกใช้เมธอดด้วยวิธีนี้คุณไม่จำเป็นต้องนำเข้าคลาสบาร์ในคลาสอื่นที่เรียกเมธอด foo ซึ่งอาจเป็นสิ่งที่คุณต้องการ

แน่นอนว่าข้อเสียคือคุณอนุญาตให้ผู้โทรตั้งค่า Bar Object

หวังว่ามันจะช่วย


3
ฉันคิดว่านี่เป็นรูปแบบการต่อต้าน ควรมีการฉีดพึ่งพาระยะเวลา การอนุญาตให้มีการพึ่งพาการฉีดแบบเลือกก็ได้เพียงเพื่อจุดประสงค์ในการทดสอบคือการหลีกเลี่ยงการปรับปรุงโค้ดและจงใจทำการทดสอบบางอย่างที่แตกต่างจากโค้ดที่ทำงานในการผลิต ทั้งสองอย่างน่ากลัวและน่ากลัวที่ต้องทำ
ErikE

8

โซลูชันสำหรับโค้ดตัวอย่างของคุณโดยใช้ PowerMockito.whenNew

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • มิถุนายน 4.12

FooTest.java

package foo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

//Both @PrepareForTest and @RunWith are needed for `whenNew` to work 
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {

    // Class Under Test
    Foo cut;

    @Mock
    Bar barMock;

    @Before
    public void setUp() throws Exception {
        cut = new Foo();

    }

    @After
    public void tearDown() {
        cut = null;

    }

    @Test
    public void testFoo() throws Exception {

        // Setup
        PowerMockito.whenNew(Bar.class).withNoArguments()
                .thenReturn(this.barMock);

        // Test
        cut.foo();

        // Validations
        Mockito.verify(this.barMock, Mockito.times(1)).someMethod();

    }

}

เอาต์พุต JUnit เอาต์พุต JUnit


8

ฉันคิดว่า Mockito @InjectMocksเป็นวิธีที่จะไป

ขึ้นอยู่กับความตั้งใจของคุณคุณสามารถใช้:

  1. ตัวสร้างการฉีด
  2. หัวฉีดคุณสมบัติการฉีด
  3. การฉีดภาคสนาม

ข้อมูลเพิ่มเติมในเอกสาร

ด้านล่างเป็นตัวอย่างของการฉีดภาคสนาม:

ชั้นเรียน:

public class Foo
{
    private Bar bar = new Bar();

    public void foo() 
    {
        bar.someMethod();
    }
}

public class Bar
{
    public void someMethod()
    {
         //something
    }
}

ทดสอบ:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
    @Mock
    Bar bar;

    @InjectMocks
    Foo foo;

    @Test
    public void FooTest()
    {
        doNothing().when( bar ).someMethod();
        foo.foo();
        verify(bar, times(1)).someMethod();
    }
}

3

ใช่ถ้าคุณต้องการ / จำเป็นต้องทำจริง ๆ คุณสามารถใช้ PowerMock นี่ควรจะเป็นทางเลือกสุดท้าย ด้วย PowerMock คุณสามารถทำให้การคืนค่าจำลองจากการเรียกไปยังตัวสร้าง จากนั้นทำการตรวจสอบบนจำลอง ที่กล่าวว่า csturtz คือคำตอบที่ "ถูกต้อง"

นี่คือลิงค์ไปสู่การสร้างจำลองของวัตถุใหม่


0

อีกวิธีง่ายๆที่จะเพิ่มคำสั่งบันทึกบางอย่างเพื่อ bar.someMethod () แล้วตรวจสอบให้แน่ใจคุณสามารถเห็นข้อความดังกล่าวเมื่อการทดสอบของคุณดำเนินการดูตัวอย่างที่นี่: วิธีการทำ JUnit ยืนยันข้อความในบันทึก

ที่เป็นประโยชน์โดยเฉพาะอย่างยิ่งเมื่อ Bar.someMethod () privateของคุณคือ

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