7

I am trying to use Constructor Injection dependency pattern.

I wonder what is the correct approach to inject JPA Repositories on Integration test classes:

I have my source code:

RepoClass

@Repository
public interface MyClassRepo extends JpaRepository<MyClass, Long> {
... methods ...
}

Service following cosntructor injection

public class MyClassService {

  private final MyClassRepo myClassRepo;

  public DeviceServiceImpl(final MyClassRepo myClassRepo) {
    this.myClassRepo = myClassRepo;
  }

  public boolean myMethodToTest() {
    ... whatever...
  }
}

To test it: (Here comes my issue)

SpringRunner class OPTION 1: Constructor injection

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MyTestConfigClass.class) // With necessary imports
@SpringBootTest
public class MyClassTester {
  private final MyClassService myClassService;
  private final MyClassRepository myClassRepository;

  public MyClassTester (final MyClassRepository deviceRepository) {
    this.myClassRepository = myClassRepository;
    this.myClassService= new myClassService(myClassRepository); 
  } 

}

Does not work since console output says:

Test class should have exactly one public zero-argument constructor

SpringRunner class OPTION 2: Autowired injection

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MyTestConfigClass.class) // With necessary imports
@SpringBootTest
public class MyClassTester {
    @Autowired
    private MyClassRepository myClassRepository;

    private MyClassService myClassService = new myClassService(myClassRepository);

}

I feel like it is breaking the desired pattern.

SpringRunner class OPTION 3: Empty Constructor

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MyTestConfigClass.class) // With necessary imports
@SpringBootTest
public class MyClassTester {

  private final MyClassService myClassService;
  private final MyClassRepository myClassRepository;

  public MyClassTester () {
    this.myClassRepository = new MyClassRepository(); // Obviously NOT working, since its an interface
    this.myClassService= new myClassService(myClassRepository); 
  } 
}

As commented: Obviously NOT working, since MyClassRepository its an interface

Is there any better approach to solve this issue?

Mayday
  • 4,680
  • 5
  • 24
  • 58
  • You could use Junit5. It allows constructors with multiple arguments. – pcoates Nov 22 '18 at 09:58
  • I don't think that the dependency injection by constructor pattern matters for tests.You're writing tests to test the functionality and the ability to do this comes first before the design patterns and so on. I don't think that any known pattern was meant for testing really. In the integration tests the context is killed when the tests are finished, so in my opinion `@Autowired` is the best and easiest way which I normally follow. It's not affecting your app in a bad way. But you should avoid that in the application code though. – Kamil Kozlowski Nov 22 '18 at 11:28

2 Answers2

6

Use Junit 5. It allows constructors with multiple arguments.

Option 1 would need @Autowired added to the test constructor

pcoates
  • 2,102
  • 1
  • 9
  • 20
2

From the documentation:

The TestContext framework does not instrument the manner in which a test instance is instantiated. Thus the use of @Autowired or @Inject for constructors has no effect for test classes.

https://docs.spring.io/autorepo/docs/spring-framework/4.2.0.RC2/spring-framework-reference/html/integration-testing.html#testcontext-fixture-di

Willem
  • 992
  • 6
  • 13