Mockito วิธีล้อเลียนเฉพาะการเรียกใช้วิธีการของ superclass


97

ฉันใช้ Mockito ในการทดสอบบางอย่าง

ฉันมีชั้นเรียนต่อไปนี้:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

ฉันต้องการที่จะเยาะเย้ยเฉพาะสายที่สอง ( super.save) ChildServiceของ โทรครั้งแรกต้องโทรตามวิธีจริง มีวิธีทำไหม?


สิ่งนี้สามารถแก้ไขได้ด้วย PowerMockito หรือไม่?
javaPlease42

@ javaPlease42: ใช่คุณสามารถ: stackoverflow.com/a/23884011/2049986
Jacob van Lingen

คำตอบ:


57

ไม่ Mockito ไม่สนับสนุนสิ่งนี้

นี่อาจไม่ใช่คำตอบที่คุณกำลังมองหา แต่สิ่งที่คุณเห็นเป็นอาการของการไม่ใช้หลักการออกแบบ:

ชอบองค์ประกอบมากกว่าการถ่ายทอดทางพันธุกรรม

หากคุณดึงกลยุทธ์ออกมาแทนที่จะขยายระดับซูเปอร์คลาสปัญหาจะหมดไป

อย่างไรก็ตามหากคุณไม่ได้รับอนุญาตให้เปลี่ยนรหัส แต่คุณต้องทดสอบอยู่ดีและด้วยวิธีที่น่าอึดอัดนี้ก็ยังมีความหวัง ด้วยเครื่องมือ AOP บางอย่าง (เช่น AspectJ) คุณสามารถสานโค้ดเป็นวิธีการระดับสูงและหลีกเลี่ยงการดำเนินการทั้งหมด (yuck) วิธีนี้ใช้ไม่ได้หากคุณใช้พร็อกซีคุณต้องใช้การปรับเปลี่ยน bytecode (ไม่ว่าจะเป็นการทอผ้าเวลาโหลดหรือการทอผ้าเวลาคอมไพล์) มีกรอบการเยาะเย้ยที่สนับสนุนเคล็ดลับประเภทนี้เช่นกันเช่น PowerMock และ PowerMockito

ฉันขอแนะนำให้คุณไปทำการ refactoring แต่ถ้านั่นไม่ใช่ตัวเลือกที่คุณต้องการเพื่อความสนุกในการแฮ็คอย่างจริงจัง


5
ฉันไม่เห็นการละเมิด LSP ฉันมีการตั้งค่าแบบเดียวกันกับ OP: คลาส DAO พื้นฐานที่มีเมธอด findAll () และคลาสย่อย DAO ที่แทนที่เมธอดพื้นฐานโดยเรียก super.findAll () แล้วเรียงลำดับผลลัพธ์ คลาสย่อยสามารถแทนที่ได้ในบริบททั้งหมดที่ยอมรับซูเปอร์คลาส ฉันเข้าใจความหมายของคุณผิดหรือเปล่า?

1
ฉันจะลบคำพูด LSP (มันไม่ได้เพิ่มมูลค่าให้กับคำตอบ)
iwein

ใช่การสืบทอดมันห่วยและกรอบงานโง่ ๆ ที่ฉันติดอยู่นั้นออกแบบมาโดยให้การสืบทอดเป็นทางเลือกเดียว
Sridhar Sarnobat

สมมติว่าคุณไม่สามารถออกแบบซูเปอร์คลาสใหม่ได้คุณสามารถแยก//some codesโค้ดออกเป็นวิธีการที่สามารถทดสอบแยกกันได้
Phasmal

1
ตกลงเข้าใจแล้ว มันเป็นปัญหาที่แตกต่างจากที่ฉันพยายามแก้ไขเมื่อฉันมองหาสิ่งนี้ แต่อย่างน้อยความเข้าใจผิดนั้นก็ช่วยแก้ปัญหาของฉันเองเพื่อล้อเลียนการโทรจากคลาสพื้นฐาน (ซึ่งฉันไม่ได้ลบล้างแน่นอน)
Guillaume Perrot

91

หากคุณไม่มีทางเลือกในการปรับโครงสร้างใหม่จริงๆคุณสามารถเยาะเย้ย / ทำลายทุกอย่างในการเรียก super method เช่น

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
รหัสนี้ไม่ได้ป้องกันการเรียก super.save () ที่ถูกต้องดังนั้นหากคุณทำมากใน super.save () คุณจะต้องป้องกันการโทรเหล่านั้นทั้งหมด ...
iwein

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

14
วิธีนี้ใช้งานได้ดีเว้นแต่จะมีการซ่อนการตรวจสอบความถูกต้องหรือวิธีการบันทึกจะทำงานโดยตรงแทนที่จะเรียกวิธีอื่น mockito ไม่: Mockito.doNothing (). เมื่อ ((BaseService) สอดแนม) .save (); สิ่งนี้จะไม่ 'ทำไม่มีอะไรในบริการฐานบันทึก แต่สำหรับเด็กบริการบันทึก :(
tibi

ฉันไม่สามารถทำให้สิ่งนี้ทำงานกับฉันได้ - มันทำให้วิธีการของเด็กเกินไป BaseServiceเป็นนามธรรมแม้ว่าฉันจะไม่เห็นว่าเหตุใดจึงเกี่ยวข้อง
Sridhar Sarnobat

1
@ Sridhar-Sarnobat อือฉันเห็นสิ่งเดียวกัน :( ใครรู้วิธีที่จะได้ไปเพียงต้นขั้วออกsuper.validate()?
stantonk

5

พิจารณาเปลี่ยนรหัสจากวิธี ChildService.save () เป็นวิธีการอื่นและทดสอบวิธีการใหม่นั้นแทนการทดสอบ ChildService.save () วิธีนี้จะช่วยหลีกเลี่ยงการเรียกใช้วิธี super โดยไม่จำเป็น

ตัวอย่าง:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

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


ฉันสามารถพูดได้ด้วยว่าองค์ประกอบของการถ่ายทอดทางพันธุกรรมนั้นดีกว่าเกือบตลอดเวลา แต่บางครั้งการใช้มรดกก็ง่ายกว่า จนกว่า java จะรวมโมเดลการจัดองค์ประกอบที่ดีขึ้นเช่น scala หรือ groovy สิ่งนี้จะเป็นเช่นนั้นเสมอและปัญหานี้จะยังคงมีอยู่
ลุ

1

แม้ว่าฉันจะเห็นด้วยกับคำตอบของ iwein โดยสิ้นเชิงก็ตาม (

ชอบองค์ประกอบมากกว่ามรดก

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

ดังนั้นคำแนะนำของฉัน:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

จากนั้นในการทดสอบหน่วย:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

เหตุผลก็คือคลาสพื้นฐานของคุณไม่ได้เผยแพร่สู่สาธารณะดังนั้น Mockito จึงไม่สามารถสกัดกั้นได้เนื่องจากการมองเห็นถ้าคุณเปลี่ยนคลาสพื้นฐานเป็นสาธารณะหรือ @Override ในคลาสย่อย (เป็นสาธารณะ) Mockito ก็สามารถล้อเลียนได้อย่างถูกต้อง

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
นี่ไม่ใช่ประเด็น ChildService ควรแทนที่ foo () และปัญหาคือวิธีการเยาะเย้ย BaseService.foo () แต่ไม่ใช่ ChildService.foo ()
Adriaan Koster

0

บางทีตัวเลือกที่ง่ายที่สุดหากการสืบทอดมีเหตุผลคือการสร้างเมธอดใหม่ (แพ็กเกจส่วนตัว ??) เพื่อเรียกซุปเปอร์ (เรียกว่า superFindall) สอดแนมอินสแตนซ์จริงแล้วเยาะเย้ยเมธอด superFindAll () ในแบบที่คุณต้องการเยาะเย้ย คลาสผู้ปกครองที่หนึ่ง ไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์แบบในแง่ของความครอบคลุมและการมองเห็น แต่ควรทำงานและสมัครง่าย

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

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