Bart W
Bart W

Reputation: 127

Using a Class with a @Required Field in Unit Test with Mockito

I'm trying to unit test a class; for the sake of brevity we'll call it Apple. It has a @Required setter that takes an AppleManager bean. AppleManager itself has a @Required setter than takes an AppleStorageClient bean. I am mocking AppleManager and injecting it into Apple, but I also need to use the real AppleManager class to retrieve data using methods from its AppleStorageClient object. How can I achieve this with Spring/Mockito?

Test:

public class AppleTest {

   @InjectMocks
   private Apple apple;

   @Mock
   private AppleManager appleManager;

   ????? 
   private AppleManager realAppleManager;
   //I tried = new AppleManager() here but the object is null...
   //ostensibly because Spring doesn't know I want to use the bean
   //also tried @Autowired to no avail

   @Before
   public void doBeforeStuff() {
      MockitoAnnotations.initMocks(this);
   }

   ...
}

Source:

public class Apple {

   private AppleManager appleManager;

   @Required
   public void setAppleManager(AppleManager appleManager) {
      this.appleManager = appleManager;
   }

   ....

}

&

public class AppleManager {

    private AppleStorageClient appleStorageClient;

       @Required
       public void setAppleStorageClient() {
          this.appleStorageClient = appleStorageClient;
       }

       ...

    }

Upvotes: 0

Views: 417

Answers (1)

Mark Bramnik
Mark Bramnik

Reputation: 42531

In general it looks like something is 'uncomplete' here. I'll explain why.

Technically If you're using spring - it doesn't sound like a unit test to me anymore, probably integration test or something. Unit tests are in general should be really-really fast and starting up spring won't let them pass fast enough (think about having thousands of unit tests in your project each of them running spring on startup - it will take them ages to complete).

But let's say its only about definitions. When you're using spring testing framework with JUnit, someone has to start and maintain a spring context to do all the Dependency Injection magic and apply it to the test case. In Junit implementation a special Runner (a JUnit abstraction) is required:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:my-test-context.xml" }) // or use Java Config

This doesn't appear in the question though.

So now Spring will create a context and will attempt to inject beans. And we have effectively reduced our issue to the issue of having two implementations of the interface and asking spring to inject implementation by interface so that two different implementations will be injected. There are 2 solutions I can see here:

  1. Create a Mock outside spring - you probably won't specify your expectations in spring anyway. Maintain only a "real apple manager" in spring

  2. Maintain both in spring but in your test case use a @Qualifier annotation

Now what I would like to emphasize is that if you maintain real apple manager that contacts "apple store" (probably a database, with driver support, transaction management and so forth) you'll have to create a test context so that it will be able to connect to that database, and if the apple manager internally injects its dependencies via spring, then these beans are also have to be specified.

So that if in future you'll change something in the underlying store (say, add a dependency in a driver to another spring bean, this test context will automatically become broken). Just be aware of this and inject beans wisely.

Upvotes: 3

Related Questions