AndrasCsanyi
AndrasCsanyi

Reputation: 4255

How to inject @Repository and multiple @Services into Spring testing context?

I'm working on a REST endpoint using Spring and I'd like to test the business logic separately in a way when it includes the mapper, repository and validation logics. They are not mocked. It is a fat integration test (if this definition exists) or a system test but the REST part is not included.

Why not mocking? I don't like them, they don't really provide any value since I define their behavior and that definition does not reflect on the code I want to test. It can be as buggy as the code I wrote. :) For me the value is when I can test the whole stack or big portion of the stack in build time, and fast. I rather manipulate the inputs to trigger behaviors and leave the mocks for behaviors cannot be triggered by inputs (do thsey make any sense?).

The problem I face is how to wire these together in a test? All the examples and articles I have found so far are 1, @DataJpaTest which focuses only the repository, 2, Controller or system tests with mocked underlying logic.

What I need is the ability to test my small libraries in a the combination (either have them mocked, not mocked or some of them mocked and some of them not) I see valuable.

Based on the articles I read so far the problem is that during testing Spring loads just a slice of the context and I need to load what I need to be there. I haven't found that how can I load into the test context what I need.

public class IAMBusinessLogicImplementation implements IAMBusinessLogicInterface {

    private final UserRepositoryInterface _userRepository;
    private final UserMapperInterface _userMappers;
    private final IAMValidatorInterface _validator;

    public IAMBusinessLogicImplementation(
        @NonNull UserRepositoryInterface _userRepository,
        @NonNull UserMapperInterface _userMappers,
        @NonNull IAMValidatorInterface _validator) {
        this._userRepository = _userRepository;
        this._userMappers = _userMappers;
        this._validator = _validator;
    }

    public UserDto createUser(@NonNull UserDto userDto) {
        // stuff
    }
}

public class IAMValidatorImplementation implements IAMValidatorInterface { }

public class UserMapperImplementation implements UserMapperInterface { }

public interface UserRepositoryInterface extends CrudRepository<User, Long> { }

Test:

@SpringBootTest(classes = {IAMBusinessLogicImplementation.class})
@EnableJpaRepositories(basePackages = "com.encyclopediagalactica.iam.tests.repository")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@AutoConfigureDataJpa
@EntityScan("com.encyclopediagalactica.iam.entities")
@ComponentScan()
public class IAMBusinessLogicTestsBase {

    @Autowired
    public IAMBusinessLogicInterface _sut;

    @Test
    public void createUser() {
        // Arrange
        UserDto userDto = UserDto.builder()
            .firstName("asd")
            .lastName("dsa")
            .build();

        // Act
        UserDto result = _sut.createUser(userDto);

        // Assert
        assertThat(result).isNotNull();
    }

}

Result:

2023-04-23T21:28:24.598+02:00 INFO 37577 --- [ main] c.e.i.t.b.IAMBusinessLogicTestsBase : Starting IAMBusinessLogicTestsBase using Java 17.0.7 with PID 37577 (started by andrascsanyi in /Users/andrascsanyi/DEV/github.com/EncyclopediaGalactica/IAM/IAM) 2023-04-23T21:28:24.601+02:00 INFO 37577 --- [ main] c.e.i.t.b.IAMBusinessLogicTestsBase : No active profile set, falling back to 1 default profile: "default" 2023-04-23T21:28:24.739+02:00 INFO 37577 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2023-04-23T21:28:24.759+02:00 INFO 37577 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8 ms. Found 0 JPA repository interfaces. 2023-04-23T21:28:24.899+02:00 INFO 37577 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2023-04-23T21:28:24.919+02:00 INFO 37577 --- [ main] org.hibernate.Version
: HHH000412: Hibernate ORM core version 6.1.7.Final 2023-04-23T21:28:25.049+02:00 INFO 37577 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2023-04-23T21:28:25.137+02:00 INFO 37577 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:testing user=SA 2023-04-23T21:28:25.138+02:00 INFO 37577 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2023-04-23T21:28:25.147+02:00 INFO 37577 --- [
main] SQL dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect 2023-04-23T21:28:25.489+02:00 INFO 37577 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator
: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2023-04-23T21:28:25.492+02:00 INFO 37577 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2023-04-23T21:28:25.495+02:00 WARN 37577 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'IAMBusinessLogicImplementation': Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'com.encyclopediagalactica.iam.repositories.UserRepositoryInterface' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} 2023-04-23T21:28:25.495+02:00 INFO 37577 --- [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2023-04-23T21:28:25.495+02:00 INFO 37577 --- [ main] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down' 2023-04-23T21:28:25.497+02:00 INFO 37577 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2023-04-23T21:28:25.498+02:00 INFO 37577 --- [
main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. 2023-04-23T21:28:25.500+02:00 INFO 37577 --- [
main] .s.b.a.l.ConditionEvaluationReportLogger :

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2023-04-23T21:28:25.508+02:00 ERROR 37577 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :

Upvotes: 0

Views: 773

Answers (1)

M. Deinum
M. Deinum

Reputation: 124934

You are trying to outsmart Spring Boot and its loading mechanisms. Don't do that as you should be working with the framework.

You have 2 options here.

  1. Use a single @SpringBootTest and load the full context (maybe with some mocked services, or in-memory database implementations).
  2. Use a dedicated and specific @TestConfiguration to load exactly what you need.

I strongly suggest option 1. as option 2 will get quite tedious and will probably lead to longer test times (as there is no reuse of a previously loaded context).

Upvotes: 2

Related Questions