Test if another method was called

前端 未结 4 1496
生来不讨喜
生来不讨喜 2021-01-04 18:33

So I\'m sure there is something like this out there but I have been searching for an hour and haven\'t found exactly what I am looking for. say I have a class that looks lik

相关标签:
4条回答
  • 2021-01-04 18:47

    Using Mockito, you can do spying on real objects like this:

    import org.junit.Test;
    import static org.mockito.Mockito.*;
    public class MyClassTest {
        @Test
        public void otherMethodShouldBeCalled() {
            MyClass myClass = new MyClass();
            MyClass spy = spy(myClass);
    
            spy.myMethod(true);
            verify(spy).otherMethod();
        }
    }
    

    There are some gotchas, so take a look at the relevant documentation as well.

    0 讨论(0)
  • 2021-01-04 18:50

    Suppose MokeysClass has a constructor declared like this, where Foo is some other class.

    public MokeysClass(String name, int counter, Foo myFoo)
    

    I would write my test like this.

    @RunWith(MockitoJUnitRunner.class)
    public class TestArray {
        @Mock 
        private Foo mockMyFoo;
        private String nameToInject = "Mokey";
        private int counterToInject = 42;
    
        @Spy 
        private MokeysClass toTest = new MokeysClass(nameToInject, counterToInject, mockMyFoo);
    
        @Test
        public void shouldCallMethod() {
            toTest.myMethod(true);
            verify(toTest).otherMethod();
        }
    }
    

    so that I am explicitly stating which constructor to call when I create my test object, and what arguments to pass to it.

    There are some reasons not to rely on @InjectMocks to do this step for me, particularly if the class being tested is more complex and has more than one constructor. Mockito chooses the constructor that has the most arguments, but if there are several constructors with the same number of arguments, Mockito could choose any of the constructors; that is, the behaviour is undefined.

    Once Mockito has chosen a constructor, it checks whether that constructor can in fact be used for constructor injection. Constructor injection will not be used if

    • one or more of the parameters of the chosen constructor is a primitive type,
    • the type of one or more of the parameters of the chosen constructor is a final class,
    • the type of one or more of the parameters of the chosen constructor is a private class,
    • the only constructor of the class is the default constructor.

    If any one of these conditions holds, for the constructor that Mockito chose, then constructor injection won’t be used. In this case, the class must have a default constructor, otherwise Mockito will throw an exception.

    The complexity of the criteria which Mockito uses when choosing whether to apply constructor injection implies that adding or removing a constructor, or changing the parameters of a constructor, can make Mockito switch from using constructor injection to using setter and field injection; or from using setter and field injection to using constructor injection. This can occur even if the constructor that is changed is not the one that will be used for constructor injection.

    As a result, any test that uses constructor injection is automatically quite brittle; in the sense that changes that are not directly related to the test itself can cause the test to fail. Such failures can be difficult to troubleshoot.

    The @InjectMocks annotation was designed for use with frameworks such as Spring that do dependency injection; and for tests of classes that use Spring, it can be invaluable. But if dependency injection is not part of your class, I would strongly recommend avoiding @InjectMocks on account of its brittleness. You really want your test code to be as easy to maintain and to troubleshoot as your production code is.

    0 讨论(0)
  • 2021-01-04 19:04

    I think you want to look at Mock objects. You can create a mock of MyClass, then set expectations that otherMethod() is called when you call myMethod and fails if it was not called.

    Here is a pretty good overview of them for java - http://www.scalatest.org/user_guide/testing_with_mock_objects

    One other major benefit of using Mocks, you can avoid side affects, like logging to NSLog or hitting a web server or printing) in your test.

    0 讨论(0)
  • 2021-01-04 19:07

    This is not recommended, but you can spy real object :)

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Spy;
    import org.mockito.runners.MockitoJUnitRunner;
    
    import static org.mockito.BDDMockito.verify;
    
    @RunWith(MockitoJUnitRunner.class)
    public class MyClassTest {
    
        @Spy
        private MyClass sut; // System Under Test
    
        @Test
        public void shouldCallMethod() {
    
            // when
            sut.myMethod(true);
    
            // then
            verify(sut).otherMethod();
        }
    }
    

    Result:

    Tests Passed: 1 passed in 0,203 s
    

    After changing code: sut.myMethod(false);

    Wanted but not invoked:
    sut.otherMethod();
    -> at my.custom.MyClassTest.shouldCallMethod(MyClassTest.java:23)
    

    Source: Spying on real objects


    Magic version with constructor injection

    @Mock
    private LexAnalyzer lexAnalyzer;
    
    @Spy
    @InjectMocks
    private SyntaxAnalyzer sut; // System Under Test
    
    @Test
    public void shouldCallMethod() {
    
        // when
        sut.myMethod(true);
    
        // then
        verify(sut).otherMethod();
    }
    

    SyntaxAnalyzer.java

    public class SyntaxAnalyzer {
    
        private final LexAnalyzer lexAnalyzer;
    
        public SyntaxAnalyzer(LexAnalyzer lexAnalyzer) {
            this.lexAnalyzer = lexAnalyzer;
        }
    ...
    

    Tested, works ;)

    0 讨论(0)
提交回复
热议问题