开发者

What is the best way to inject mocked Spring @Autowired dependencies from a unit test?

import org.springframework.beans.factory.annotation.Autowired;

class MyService {
  @Autowired private DependencyOne dependencyOne;
  @Autowired private DependencyTwo dependencyTwo;

  public void doSomething(){
    //Does something with dependencies
  }
}

When testing this class, I basically have four ways to inject mock dependencies:

  1. Use Spring's ReflectionTestUtils in the test to inject the dependencies
  2. Add a constructor to MyService
  3. Add setter methods to MyService
  4. Relax the dependency visibility to package-protected and set the fields directly

Which is the best and why?

--- UPDATE ---

I guess I should have been a bit clearer - I am only t开发者_开发百科alking about "unit" style tests, not Spring "integration" style tests where dependencies can be wired in using a Spring context.


Use ReflectionTestUtils or put a setter. Either is fine. Adding constructors can have side effects (disallow subclassing by CGLIB for example), and relaxing the visibility just for the sake of testing is not a good approach.


Spring's ContextConfiguration can do this for you.

For example, in the test context below the "Local" classes are mocks. NotificationService is the class I want to test.

I am using component scanning to bring the mocks into the context but you can just as easily use <bean> declarations. Note the use of use-default-filters="false".

<context:component-scan base-package="com.foo.config" use-default-filters="false">
    <context:include-filter type="assignable" 
        expression="com.foo.LocalNotificationConfig"/>
</context:component-scan>

<context:component-scan base-package="com.foo.services.notification"
        use-default-filters="false">
    <context:include-filter type="assignable"
        expression="com.foo.services.notification.DelegatingTemplateService"/>
    <context:include-filter type="assignable"
        expression="com.foo.services.notification.NotificationService"/>
</context:component-scan>

<context:component-scan base-package="com.foo.domain"/>

DelegatingTemplateService is a Groovy class with a @Delegate.

class DelegatingTemplateService {
  @Delegate
  TemplateService delegate
}

In the test class I use the test context and inject the service to test. In the setup I set the delegate of the DelegatingTemplateService:

@RunWith(classOf[SpringJUnit4ClassRunner])
@ContextConfiguration(Array("/spring-test-context.xml"))
class TestNotificationService extends JUnitSuite {
  @Autowired var notificationService: NotificationService = _
  @Autowired var templateService: DelegatingTemplateService = _

  @Before
  def setUp {
    templateService.delegate = /* Your dynamic mock here */
  }  

In the service the @Autowired fields are private:

@Component("notificationService")
class NotificationServiceImpl extends NotificationService {
  @Autowired private var domainManager: DomainManager = _
  @Autowired private var templateService: TemplateService = _
  @Autowired private var notificationConfig: NotificationConfig = _


2) Use @Autowired constructor injection (if that's what option 2 was; otherwise, make an option 5)

Proper constructors that create an object in a valid state is a more correctly object-oriented approach, and losing cglib proxies is relatively unimportant since we all code to interfaces anyway, right??

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜