Тестовый класс с вызовом new () в Mockito - PullRequest
56 голосов
/ 07 мая 2011

У меня есть устаревший класс, который содержит вызов new () для создания экземпляра LoginContext ():

public class TestedClass {
  public LoginContext login(String user, String password) {
    LoginContext lc = new LoginContext("login", callbackHandler);
  }
}

Я хочу протестировать этот класс, используя Mockito для макета LoginContext, так как он требует, чтобы перед созданием экземпляра были настроены средства безопасности JAAS, но я не уверен, как это сделать, не изменяя метод login () для вывода LoginContext. Возможно ли использовать Mockito для насмешки над классом LoginContext?

Ответы [ 6 ]

51 голосов
/ 07 мая 2011

В будущем я бы порекомендовал ответ Эрана Харела (рефакторинг переместил new на фабрику, которую можно высмеивать).Но если вы не хотите изменять исходный код, используйте очень удобную и уникальную функцию: шпионы .Из документации :

Вы можете создавать шпионов из реальных объектов.Когда вы используете шпиона, тогда вызываются методы real (если метод не был заглушен).

Настоящих шпионов следует использовать осторожно , иногда , например, при работе с устаревшим кодом.

В вашем случае вы должны написать:

TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);
31 голосов
/ 09 мая 2015

Я полностью за решение Эрана Харела, и в случаях, когда это невозможно, предложение Томаша Нуркевича о шпионаже превосходно.Однако стоит отметить, что бывают ситуации, когда ни один из них не применим.Например, если метод login был немного "более точным":

public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
}

... и это был старый код, который не мог быть реорганизован для извлечения инициализации нового LoginContext в его собственный методи примените одно из вышеупомянутых решений.

Для полноты картины стоит упомянуть третий метод - использование PowerMock для введения фиктивного объекта при вызове оператора new.PowerMock не серебряная пуля, хотя.Он работает, применяя манипулирование байт-кодом к классам, которые он симулирует, что может быть изворотливой практикой, если в тестируемых классах используется манипуляция или отражение байт-кода, и, по крайней мере, из моего личного опыта, это приводит к снижению производительности теста.Опять же, если нет других вариантов, единственным вариантом должен быть хороший вариант:

@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {

    @Test
    public void testLogin() {
        LoginContext lcMock = mock(LoginContext.class);
        whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
        TestedClass tc = new TestedClass();
        tc.login ("something", "something else");
        // test the login's logic
    }
}
21 голосов
/ 07 мая 2011

Вы можете использовать фабрику для создания контекста входа в систему. Затем вы можете издеваться над фабрикой и вернуть для теста все, что захотите.

public class TestedClass {
  private final LoginContextFactory loginContextFactory;

  public TestedClass(final LoginContextFactory loginContextFactory) {
    this.loginContextFactory = loginContextFactory;
  }

  public LoginContext login(String user, String password) {
    LoginContext lc = loginContextFactory.createLoginContext();
  }
}

public interface LoginContextFactory {
  public LoginContext createLoginContext();
}
4 голосов
/ 17 сентября 2017
    public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
  }

- Тестовый класс:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(TestedClass.class)
    public class TestedClassTest {

        @Test
        public void testLogin() {
            LoginContext lcMock = mock(LoginContext.class);
            whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
//comment: this is giving mock object ( lcMock )
            TestedClass tc = new TestedClass();
            tc.login ("something", "something else"); ///  testing this method.
            // test the login's logic
        }
    }

При вызове фактического метода tc.login ("something", "something else"); из testLogin () {- Этот LoginContext lc имеет значение null и выдает NPE при вызове lc.doThis();

3 голосов
/ 07 мая 2011

Не то, что я знаю, но как насчет того, чтобы сделать что-то подобное при создании экземпляра TestedClass, который вы хотите протестировать:

TestedClass toTest = new TestedClass() {
    public LoginContext login(String user, String password) {
        //return mocked LoginContext
    }
};

Другой вариант - использовать Mockito для создания экземпляра TestedClass и позволить смоделированному экземпляру вернуть LoginContext.

1 голос
/ 11 декабря 2017

В ситуациях, когда тестируемый класс может быть изменен, и когда желательно избежать манипуляций с байтовым кодом, чтобы все было быстро или чтобы минимизировать сторонние зависимости, вот мое мнение об использовании фабрики для извлечения new операция.

public class TestedClass {

    interface PojoFactory { Pojo getNewPojo(); }

    private final PojoFactory factory;

    /** For use in production - nothing needs to change. */
    public TestedClass() {
        this.factory = new PojoFactory() {
            @Override
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
    }

    /** For use in testing - provide a pojo factory. */
    public TestedClass(PojoFactory factory) {
        this.factory = factory;
    }

    public void doSomething() {
        Pojo pojo = this.factory.getNewPojo();
        anythingCouldHappen(pojo);
    }
}

С этим все ваши тесты, утверждения и проверки вызовов для объекта Pojo просты:

public  void testSomething() {
    Pojo testPojo = new Pojo();
    TestedClass target = new TestedClass(new TestedClass.PojoFactory() {
                @Override
                public Pojo getNewPojo() {
                    return testPojo;
                }
            });
    target.doSomething();
    assertThat(testPojo.isLifeStillBeautiful(), is(true));
}

Единственный недостаток этого подхода может возникнуть, если TestClass имеет несколько конструкторов, которые вам придется дублировать с дополнительным параметром.

По твердым причинам вы, вероятно, захотите вместо этого поместить интерфейс PojoFactory в класс Pojo, а также на производственную фабрику.

public class Pojo {

    interface PojoFactory { Pojo getNewPojo(); }

    public static final PojoFactory productionFactory = 
        new PojoFactory() {
            @Override 
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
...