Mockito : 메소드 내에서 생성 된 객체에서 메소드를 확인하는 방법은 무엇입니까?


322

나는 Mockito를 처음 사용합니다.

아래 클래스가 주어지면 Mockito를 사용하여 someMethod호출 한 후 정확히 한 번 호출 되었는지 어떻게 확인할 수 foo있습니까?

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

다음과 같은 확인 전화를하고 싶습니다.

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

bar의 조롱 된 인스턴스는 어디에 있습니까 Bar?


2
stackoverflow.com/questions/6520242/…- 그러나 PowerMock을 사용하고 싶지 않습니다.
mre

API 또는 PowerMock을 변경하십시오. 둘 중 하나.
John B

이런 식으로 덮는 방법 ?? 공개 동기화 된 void start (BundleContext bundleContext) 예외 발생 {BundleContext bc = bundleContext; logger.info ( "STARTING HTTP 서비스 번들"); this.tracker = new ServiceTracker (bc, HttpService.class.getName (), null) {@public 객체 추가 서비스 추가 서비스 (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); httpService를 반환; }}}
ShKkKiR

답변:


365

의존성 주입

Bar 인스턴스 또는 Bar 인스턴스를 만드는 데 사용되는 팩토리 (또는 다른 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에 대한 대화입니다. 또는 Bar 객체를 생성하는 것은 클래스 Foo의 foo () 메소드의 책임입니다. 대답이 '예'이면 구현 세부 사항이므로 상호 작용을 구체적으로 테스트하는 것에 대해 걱정할 필요가 없습니다 (@Michael의 답변 참조). 대답이 '아니요'인 경우 테스트의 어려움이 디자인을 약간 개선해야한다는 적신호이기 때문에 클래스를 수정하는 것입니다.
csturtz

3
"실제"객체를 Mockito의 "확인"에 전달할 수 있습니까?
John B

4
공장을 조롱 할 수도 있습니다. BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

고전적인 반응은 "그렇지 않다"입니다. Foo내부 API가 아닌 의 공개 API를 테스트합니다 .

영향을받는 Foo개체 (또는 환경의 다른 개체)의 동작이 foo()있습니까? 그렇다면 테스트하십시오. 그렇지 않은 경우 방법은 무엇을합니까?


4
실제로 여기서 무엇을 테스트 하시겠습니까? 의 공개 API FooIS public void foo()내부가됩니다 관련 구비되어 있습니다.
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 클래스를 가져올 필요가 없습니다.

물론 단점은 호출자가 막대 객체를 설정할 수 있다는 것입니다.

도움이 되길 바랍니다.


3
나는 이것이 반 패턴이라고 생각한다. 의존성, 기간을 주입해야합니다. 테스트 목적으로 만 선택적으로 주입 된 종속성을 허용하는 것은 의도적으로 코드 개선을 피하고 프로덕션에서 실행되는 코드와 다른 것을 의도적으로 테스트하는 것입니다. 둘 다 끔찍하고 끔찍한 일입니다.
ErikE

8

예제 코드를 사용하는 솔루션 PowerMockito.whenNew

  • mockito-all 1.10.8
  • 파워 코어 1.6.1
  • powermock- 모듈 -junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 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는 "올바른"답변입니다.

새로운 객체의 모의 구성에 대한 링크는 다음과 같습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.