How do I use Spring Dependency Injection for a class that has stored data?

107 views Asked by At

I have a class which stores data, and needs access to an external service. How do I configure this class so that I can instantiate it with data, while still allowing the ability to inject a mock of the service in tests?

@Service
@Scope("prototype")
final class Adapter {
    @Autowired private AdapterService adapterService;
    private final Options s;

    @Autowired
    Adapter(@NotNull Options options) {
        this.options = options;
    }
}
@Test
void adapter() {
    // How do I inject mock(AdapterService.class)?
    Adapter adapter = applicationContext.getBean(Adapter.class, options);
}

Some hacky workarounds I've played with:

  1. TestOnly Constructor
  2. TestOnly Setter to override adapterService
  3. Setting the adapterService property via reflection

Please tell me that these aren't the only options, and that Spring provides support for this common use-case!

1

There are 1 answers

2
Mark Bramnik On

I suppose you're using the tests without spring (plain unit tests) with JUnit or something. In this case, I suggest moving from the field injection (that you use to inject the service) to the constructor injection. Your class will look like this:

@Service
@Scope("prototype")
final class Adapter {
    private final AdapterService adapterService; // note, I've removed an @Autowired annotation
    private final Options s;

    @Autowired // if this is a single constructor, this annotation is not mandatory
    Adapter(AdapterService adapterService, @NotNull Options options) {
        this.adapterService = adapterService;
        this.options = options;
    }
}

I see that the Adapter is a prototype but Spring will still be able to inject a singleton instance of the AdapterService as long as it's also defined by Spring (it is a bean by itself).

Now in a Unit test you will create an Adapter class and can pass now as many mocks as you like:


class AdapterTest {
  
  @Test  
  void mytest() {
     AdapterService adapterService = Mockito.mock(AdapterService.class);
     Options options = ...;
     Adapter underTest = new Adapter(adapterService, options);
  }
}