jean
jean

Reputation: 2990

mock autowired field in@Import class

I am doning an unit test which require some objects which injected by spring so I use:

@RunWith(SpringRunner.class)
@Import({BConfig.class})
public class ATest {
    private A a;

    @Autowired
    private B b;

    @Before
    public void init() {
        a = new A(b);
    }
}

However, the BConfig class has an autowired field c which I don' need in this test:

class BConfig {
    @Autowired
    C c;

    @Bean
    public getB() {
        return new B();
    }

    // other method code will use field c
}

The autowired c field will get data from redis in @PostConstruct which don't exist in unit test. If I don't omit that, the unit test will report error due to redis data is not exist.

I have a solution to make C to 2 subclasses CProduction and CUnitTest both of them implements interface C, then active profile to use CUnitTest in unit test. However this is some kind of invasive because if don't do the unit test, the interface of C is useless.

Is there a better way to do this?

Upvotes: 0

Views: 62

Answers (1)

Mark Bramnik
Mark Bramnik

Reputation: 42541

Consider using:

@RunWith(SpringRunner.class)
@ContextConfiguration({BConfig.class})
class ATest {

   @MockBean // will place a mock implementation of C to the context instead of real one that tries to connect with Redis.
   C c;

   ...
}

Note that it will work seamlessly if C is an Interface. Otherwise it will try to create a proxy by inheritance (something that extends from C) and if C has some redis-related code in constructor it might still attempt to call it.

Update 1

Based on OP's comment in an attempt to clarify the answer:

When spring application context starts it basically resolves all the beans and injects what's needed.

First it resolves the bean definitions (metadata) - the real class, scope, etc. And then it creates beans in the order that will allow injection. For example, it class A has a field of class B (both are beans) then spring must create B first, then create a and inject B into A.

Now, @MockBean is a hook relevant for tests. It tells spring application context used in test that instead of a regular bean definition that spring is able to parse out of @Configuration classes or find the component due to @Component annotation placed on it, it should use the Mock - something that is generated in runtime with frameworks like Mockito.

This mock implementation can later be used to specify the expectations (see Mockito basic tutorial, there are plenty of those on internet), but what's more important for your case, it wont connect to redis because this mock implementation doesn't have any redis related code.

Update 2

The configuration should be rewritten as follows:

@Configuration
public class BConfig {

    @Bean
    public getB() {
        return new B();
    }
    @Bean
    public C c() {
       return new C();
    }


    // other method code will use field c:
    // if for example D uses C: then you can:

    @Bean
    public D d(C c) {
     return new D(c);
    } 
}

Upvotes: 2

Related Questions