简体   繁体   English

Java:当该字段未公开时,如何模拟字段的方法?

[英]Java: How do I mock a method of a field when that field isn't exposed?

I'm using Java 6, JUnit 4.8.1, and writing a console application. 我正在使用Java 6,JUnit 4.8.1,并编写控制台应用程序。 My application has a member field that isn't exposed … 我的应用程序有一个未公开的成员字段...

public class MyApp { 
    ...
    private OpportunitiesService m_oppsSvc;

    private void initServices() { 
        …
        m_oppsSvc = new OpportunitiesServiceImpl(…);
    }
    ...
}

I want to mock a behavior such that whenever one method from my service is called, (eg m_oppsSvc.getResults() ), the same result is always returned. 我想模拟一种行为,这样无论何时调用我的服务中的一个方法(例如m_oppsSvc.getResults() ),总会返回相同的结果。 How do I do that? 我怎么做? There's no setter method for the field. 这个领域没有setter方法。 I'm currently working with Mockito 1.8.4. 我目前正在使用Mockito 1.8.4。 Is it possible to do this with Mockito or some other mock framework? 是否有可能使用Mockito或其他一些模拟框架?

This is what you want: 这就是你想要的:

@RunWith(MockitoJUnitRunner.class)
public class MyAppTest { 

    @Mock private OpportunitiesService mocked_m_oppsSvc;
    @InjectMocks MyApp myApp;

    @Test public void when_MyApp_uses_OpportunititesService_then_verify_something() { 
        // given
        given( mocked_m_oppsSvc.whatever()).willReturn(...);

        // when
        myApp.isUsingTheOpportunitiesService(...);

        // then
        verify...
        assertThat...
    }
}

Using: Mockito 1.9.0 , BDD style , FEST-Assert AssertJ . 使用: Mockito 1.9.0BDD风格 FEST-Assert AssertJ

Hope that helps :) 希望有帮助:)

Given that you're already using mockito, why not just use reflection: 鉴于你已经在使用mockito,为什么不使用反射:

@RunWith(MockitoJUnitRunner.class)
public class MyApp { 

    @Mock
    private OpportunitiesService m_oppsSvc;

    private MyApp myApp;


    @Before
    public void before() throws Exception {
       myApp = new MyApp();
       Field f = MyApp.class.getDeclaredField("m_oppsSvc");
       f.setAccessible(true);
       f.set(myApp, m_oppsSvc);
    }
}

It's a bit ugly, but it will do the trick. 这有点难看,但它会起作用。 Note that this may not be the most efficient way to do it with Mockito, but it will work. 请注意,这可能不是使用Mockito执行此操作的最有效方法,但它可以正常工作。

There's also Powermock which should allow you to do this as well using the Whitebox class. 还有Powermock应该允许你使用Whitebox类这样做。 I won't get into the whole details of Powermock but here's the call to inject the private field value, which should be a mock object: 我不会深入了解Powermock的所有细节,但这里是注入私有字段值的调用,它应该是一个模拟对象:

Whitebox.setInternalState(myApp, "m_oppsSvc", m_oppsSvc);

You should consider attempts to mock a private field a smell . 你应该考虑尝试模仿私人领域的气味 That is, a sign that either what you're trying to do is either incorrect or that your code is currently structured incorrectly. 也就是说,表示您要执行的操作不正确或者您的代码当前结构不正确。 You should only need to mock public methods or injected dependencies 您应该只需要模拟公共方法或注入依赖项

In the code you've given you should consider injecting OpportunitiesService as follows: 在您给出的代码中,您应该考虑注入OpportunitiesService,如下所示:

public class MyApp { 
    ...
    private OpportunitiesService m_oppsSvc;

    public MyApp(OpportunitiesService oppsSvc) {
        this.m_oppsSvc = oppsSvc;
    }
    ...
}

In your test you can then inject a mock as follows: 在您的测试中,您可以按如下方式注入模拟:

OpportunitiesService mockOpportunitiesService =
    Mockito.mock(OpportunitiesService.class);
Mockit.when(mockOpportunitiesService.someMethod()).thenReturn(someValue);
MyApp app = new MyApp(mockOpportunitiesService);

You can easily do it with JMockit: 您可以使用JMockit轻松完成:

public class MyAppTest
{
    @Tested MyApp myApp;

    @Test
    public testSomething(final @Capturing OpportunitiesService mockService)
    {
        new NonStrictExpectations() {{
            mockService.getResults(); result = asList("a", "b", "C");
            // record other expectations, if needed
        }};

        myApp.whateverMethodIWantToTest();

        new Verifications() {{
            mockService.doSomething(anyInt);
            // verify other expectations, if needed
        }};
    }
}

Even though the implementation class OpportunitiesServiceImpl isn't mentioned in test code, its instances (any number of them) will still get properly mocked. 尽管测试代码中未提及实现类OpportunitiesServiceImpl ,但其实例(任意数量)仍将被正确模拟。

Generally you should use dependency injection and pass the mock object (of type OppportunitiesServiceImpl) in via the constructor, a separate setter or directly to the method (getResults). 通常,您应该使用依赖注入并通过构造函数,单独的setter或直接向方法(getResults)传递模拟对象(类型为OppportunitiesServiceImpl)。 You might need to extract an interface for OpportunitiesServiceImpl first. 您可能需要首先为OpportunitiesServiceImpl提取接口。

Usually, this is solved through the use of dependency injection. 通常,这是通过使用依赖注入来解决的。 In regular (production) mode, your dependency injection container (eg Spring or Guice) will inject an instance of OpportunitiesService into MyApp through your constructor or through a setter. 在常规(生产)模式下,依赖注入容器(例如Spring或Guice)将通过构造函数或setter将OpportunitiesService实例注入MyApp

Then, when you're testing you can "inject" a mock instance manually using the same setter or constructor argument. 然后,当您进行测试时,您可以使用相同的setter或构造函数参数手动“注入”模拟实例。

Instead of doing 而不是做

m_oppsSvc = new OpportunitiesServiceImpl(…);

Try Passing OpportunitesService in through MyApp 's constructor 尝试通过MyApp的构造函数传递OpportunitesService

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM