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:
- Use Spring's ReflectionTestUtils in the test to inject the dependencies
- Add a constructor to MyService
- Add setter methods to MyService
- 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??
精彩评论