การจำลองวิธีแบบคงที่ด้วย Mockito


372

ฉันได้เขียนโรงงานเพื่อผลิตjava.sql.Connectionสิ่งของ:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

ฉันต้องการตรวจสอบความถูกต้องของพารามิเตอร์ที่ส่งไปยังDriverManager.getConnectionแต่ฉันไม่ทราบวิธีการจำลองวิธีแบบคงที่ ฉันใช้ JUnit 4 และ Mockito สำหรับเคสทดสอบของฉัน มีวิธีที่ดีในการเยาะเย้ย / ตรวจสอบกรณีการใช้งานที่เฉพาะเจาะจงนี้หรือไม่?


1
สิ่งนี้จะช่วยได้ไหม stackoverflow.com/questions/19464975/…
sasankad

5
คุณไม่สามารถทำได้ด้วย mockito โดย desing :)
MariuszS

25
@MariuszS มันไม่ได้เกิดจากการออกแบบที่ Mockito (หรือ EasyMock หรือ jMock) ไม่รองรับการเยาะเย้ยstaticวิธี แต่โดยอุบัติเหตุ ข้อ จำกัด นี้ (รวมถึงการไม่สนับสนุนfinalคลาส / วิธีการเยาะเย้ยหรือnewวัตถุที่มีการจำลอง) เป็นผลมาจากธรรมชาติของวิธีการที่ใช้ในการเยาะเย้ยโดยที่คลาสใหม่จะถูกสร้างขึ้นแบบไดนามิก ห้องสมุดที่เยาะเย้ยคนอื่นใช้วิธีการอื่น ๆ เรื่องนี้เกิดขึ้นใน. NET โลกเช่นกัน
Rogério

2
@ Rogérioขอบคุณสำหรับคำอธิบาย github.com/mockito/mockito/wiki/FAQ ฉันสามารถเยาะเย้ยวิธีการคงที่ได้หรือไม่ ไม่ Mockito ชอบการวางแนวของวัตถุและการฉีดพึ่งพารหัสคงที่ขั้นตอนที่ยากต่อการเข้าใจและเปลี่ยนแปลง มีบางคือการออกแบบที่อยู่เบื้องหลังข้อ จำกัด นี้มากเกินไป :)
MariuszS

17
@MariuszS ฉันอ่านว่าเป็นความพยายามที่จะยกเลิกกรณีการใช้งานที่ถูกต้องตามกฎหมายแทนการยอมรับเครื่องมือที่มีข้อ จำกัด ที่ไม่สามารถลบได้ (ง่าย) และโดยไม่ต้องให้เหตุผลที่สมเหตุสมผล BTW นี่คือการสนทนาสำหรับมุมมองตรงกันข้ามกับการอ้างอิง
Rogério

คำตอบ:


350

ใช้PowerMockitoที่ด้านบนของ Mockito

รหัสตัวอย่าง:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void shouldVerifyParameters() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute(); // System Under Test (sut)

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }

ข้อมูลมากกว่านี้:


4
ในขณะที่งานนี้ในทางทฤษฎีมีช่วงเวลาที่ยากลำบากในการปฏิบัติ ...
Naftuli Kay

38
โชคไม่ดีที่ข้อเสียอย่างใหญ่หลวงนี้จำเป็นสำหรับ PowerMockRunner
Innokenty

18
sut.execute ()? หมายถึง?
TejjD

4
ระบบภายใต้การทดสอบคลาสที่ต้องการจำลองของ DriverManager kaczanowscy.pl/tomek/2011-01/testing-basics-sut-and-docs
MariuszS

8
FYI ถ้าคุณกำลังใช้ JUnit4 ที่คุณสามารถทำได้และด้านล่างว่า@RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(JUnit4.class)
EM-Creations

71

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

วัตถุห่อหุ้มกลายเป็นส่วนหน้าของคลาสแบบคงที่จริงและคุณไม่ได้ทดสอบสิ่งเหล่านั้น

วัตถุห่อหุ้มอาจเป็นอะไรก็ได้

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

ในที่สุดชั้นเรียนของคุณภายใต้การทดสอบสามารถใช้วัตถุเดี่ยวนี้โดยมีตัวสร้างเริ่มต้นสำหรับการใช้งานจริง:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

และที่นี่คุณมีคลาสที่สามารถทดสอบได้ง่ายเพราะคุณไม่ได้ใช้คลาสโดยตรงกับวิธีการคงที่

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


3
ฉันสร้างเครื่องมือเพื่อสร้างอินเทอร์เฟซ Java 8 "มิกซ์อิน" โดยอัตโนมัติซึ่งห่อสายเรียกเข้า: github.com/aro-tech/interface-itมิกซ์อินที่สร้างขึ้นสามารถเยาะเย้ยเหมือนส่วนต่อประสานอื่น ๆ หรือถ้าชั้นเรียนของคุณทดสอบ คุณสามารถลบล้างวิธีการใด ๆ ในคลาสย่อยสำหรับการทดสอบ
aro_tech

25

ฉันมีปัญหาที่คล้ายกัน คำตอบที่ยอมรับไม่ได้ผลสำหรับฉันจนกว่าฉันจะทำการเปลี่ยนแปลง: @PrepareForTest(TheClassThatContainsStaticMethod.class)ตามเอกสารของ PowerMock สำหรับ mockStaticmockStatic

BDDMockitoและผมก็ไม่ต้องใช้

ห้องเรียนของฉัน:

public class SmokeRouteBuilder {
    public static String smokeMessageId() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("Exception occurred while fetching localhost address", e);
            return UUID.randomUUID().toString();
        }
    }
}

ชั้นทดสอบของฉัน:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
    @Test
    public void testSmokeMessageId_exception() throws UnknownHostException {
        UUID id = UUID.randomUUID();

        mockStatic(InetAddress.class);
        mockStatic(UUID.class);
        when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
        when(UUID.randomUUID()).thenReturn(id);

        assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
    }
}

ไม่สามารถคิดออก? .mockStatic และ?. เมื่อใดที่มี JUnit 4
Teddy

PowerMock.mockStatic & Mockito.when ไม่ทำงาน
เท็ดดี้

สำหรับทุกคนที่เห็นสิ่งนี้ในภายหลังสำหรับฉันฉันต้องพิมพ์ PowerMockito.mockStatic (StaticClass.class)
คิด

คุณต้องรวม arterfact ของ powermock-api-mockito maven
PeterS

23

ดังที่ได้กล่าวไว้ก่อนหน้านี้คุณไม่สามารถจำลองวิธีการคงที่ด้วย mockito

หากการเปลี่ยนกรอบการทดสอบของคุณไม่ใช่ตัวเลือกคุณสามารถทำสิ่งต่อไปนี้:

สร้างอินเทอร์เฟซสำหรับ DriverManager จำลองอินเทอร์เฟซนี้ฉีดผ่านการฉีดพึ่งพาบางชนิดและตรวจสอบจำลองที่


7

การสังเกต: เมื่อคุณเรียกใช้เมธอดสแตติกภายในเอนทิตีแบบคงที่คุณต้องเปลี่ยนคลาสใน @PrepareForTest

ตัวอย่างเช่น:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

สำหรับรหัสข้างต้นหากคุณต้องการเยาะเย้ยคลาส MessageDigest ให้ใช้

@PrepareForTest(MessageDigest.class)

ในขณะที่ถ้าคุณมีสิ่งที่ชอบด้านล่าง:

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}

จากนั้นคุณจะต้องเตรียมคลาสที่มีรหัสนี้

@PrepareForTest(CustomObjectRule.class)

จากนั้นก็เลียนแบบวิธีการ:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

ฉันต่อสู้กับกำแพงพยายามคิดออกว่าทำไมชั้นเรียนของฉันจึงไม่เยาะเย้ย คุณจะคิดว่าทุกบทช่วยสอนใน interwebs จะมีใครเข้าไปมากกว่ากรณีใช้กระดูกเปล่า
SoftwareSavant

6

คุณสามารถทำได้ด้วยการรีแฟคเตอร์เล็กน้อย:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return _getConnection(...some params...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //method to forward parameters, enabling mocking, extension, etc
    Connection _getConnection(...some params...) throws SQLException {
        return DriverManager.getConnection(...some params...);
    }
}

จากนั้นคุณสามารถขยายคลาสของคุณMySQLDatabaseConnectionFactoryเพื่อกลับสู่การเชื่อมต่อที่เยาะเย้ยทำสิ่งที่ยืนยันในพารามิเตอร์และอื่น ๆ

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

public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {

    Connection _getConnection(...some params...) throws SQLException {
        if (some param != something) throw new InvalidParameterException();

        //consider mocking some methods with when(yourMock.something()).thenReturn(value)
        return Mockito.mock(Connection.class);
    }
}

6

จะเยาะเย้ยวิธีการแบบคงที่คุณควรใช้รูปลักษณ์ Powermock ที่: https://github.com/powermock/powermock/wiki/MockStatic Mockito ไม่ได้ให้บริการฟังก์ชั่นนี้

คุณสามารถอ่านบทความเกี่ยวกับ mockito ได้ที่: http://refcardz.dzone.com/refcardz/mockito


2
กรุณาอย่าเชื่อมโยงไปยังเว็บไซต์ คำตอบควรมีคำตอบที่ใช้ได้จริง หากไซต์หยุดทำงานหรือเปลี่ยนแปลงคำตอบจะไม่ถูกต้องอีกต่อไป
the_new_mr

6

Mockito ไม่สามารถจับวิธีคงที่ แต่เนื่องจากMockito 2.14.0คุณสามารถจำลองได้โดยการสร้างอินสแตนซ์การเรียกใช้ของวิธีการคงที่

ตัวอย่าง (สกัดจากการทดสอบ ):

public class StaticMockingExperimentTest extends TestBase {

    Foo mock = Mockito.mock(Foo.class);
    MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
    Method staticMethod;
    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
        @Override
        public Object call() throws Throwable {
            return null;
        }
    };

    @Before
    public void before() throws Throwable {
        staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
    }

    @Test
    public void verify_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        handler.handle(invocation);

        //verify staticMethod on mock
        //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
        //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
        //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
        verify(mock);
        //2. Create the invocation instance using the new public API
        //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
        Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        //3. Make Mockito handle the static method invocation
        //  Mockito will find verification mode in thread local state and will try verify the invocation
        handler.handle(verification);

        //verify zero times, method with different argument
        verify(mock, times(0));
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        handler.handle(differentArg);
    }

    @Test
    public void stubbing_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "foo");
        handler.handle(invocation);

        //register stubbing
        when(null).thenReturn("hey");

        //validate stubbed return value
        assertEquals("hey", handler.handle(invocation));
        assertEquals("hey", handler.handle(invocation));

        //default null value is returned if invoked with different argument
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        assertEquals(null, handler.handle(differentArg));
    }

    static class Foo {

        private final String arg;

        public Foo(String arg) {
            this.arg = arg;
        }

        public static String staticMethod(String arg) {
            return "";
        }

        @Override
        public String toString() {
            return "foo:" + arg;
        }
    }
}

เป้าหมายของพวกเขาคือไม่สนับสนุนการเยาะเย้ยโดยตรง แต่เพื่อปรับปรุง API สาธารณะเพื่อให้ไลบรารี่อื่น ๆ เช่นPowermockitoไม่จำเป็นต้องพึ่งพา API ภายในหรือต้องทำซ้ำโค้ด Mockito โดยตรง (ที่มา )

คำเตือน: ทีม Mockito คิดว่าถนนสู่นรกเต็มไปด้วยวิธีการคงที่ อย่างไรก็ตามงานของ Mockito ไม่ได้เป็นการป้องกันรหัสของคุณจากวิธีการคงที่ หากคุณไม่ต้องการให้ทีมของคุณทำการเยาะเย้ยแบบคงที่ให้หยุดใช้ Powermockito ในองค์กรของคุณ Mockito จำเป็นต้องพัฒนาเป็นชุดเครื่องมือที่มีวิสัยทัศน์ที่มีความคิดเห็นเกี่ยวกับวิธีการทดสอบ Java ที่ควรเขียน อย่างไรก็ตาม Mockito ไม่เชื่อฟัง เราไม่ต้องการปิดกั้นกรณีการใช้ที่ไม่ได้รับการแนะนำเช่นการเยาะเย้ยแบบคงที่ มันไม่ใช่งานของเรา



1

เนื่องจากวิธีการนี้เป็นแบบสแตติกจึงมีทุกสิ่งที่คุณจำเป็นต้องใช้ดังนั้นจึงเอาชนะวัตถุประสงค์ในการเยาะเย้ย การเยาะเย้ยวิธีการแบบคงที่ถือว่าเป็นวิธีที่ไม่ดี

หากคุณพยายามทำสิ่งนั้นหมายความว่ามีบางอย่างผิดปกติกับวิธีที่คุณต้องการทำการทดสอบ

แน่นอนคุณสามารถใช้ PowerMockito หรือกรอบงานอื่น ๆ ที่มีความสามารถในการทำเช่นนั้น แต่ลองคิดทบทวนแนวทางของคุณใหม่

ตัวอย่างเช่น: ลองจำลอง / จัดเตรียมวัตถุซึ่งวิธีการคงที่นั้นใช้แทน


0

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

@ จำลองด้านล่างเป็นแพคเกจที่จำลองขึ้น

Connection jdbcConnection = Mockito.mock(Connection.class);

MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {

            DBConnection singleton = new DBConnection();

            @Mock
            public DBConnection getInstance() { 
                return singleton;
            }

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