1

I want to mock a method call inside @PostConstruct. During normal application start, this initializes some data from a database.

But during a test or integration test, I want to mock that database call and return my mocked Set<String> instead.

Problem: @PostConstruct is always called before the mock is set up in @Before method:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockITest {
    @MockBean
    private DatabaseService db;

    @Autowird
    private MyService service; //the service to test

    @Before
    public void setup() {
        given(db.findAllByName()).willReturn(Set.of("john", "doe"));
    }

    @Test
    public void testServiceInPostConstructIsMocked() {
        service.run();
    }
}

public class MyService {
    @Autowired
    private DatabaseService db;

    private Set<String> names;

    //initialization on startup, and cache the results in the member field
    @PostConstruct
    public void init() {
        names = db.findAllByName();     
    }

    public void run() {
        System.out.println(names); //always empty for the test
    }
}

How can I still mock the database service properly?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • Did you try @BeforeClass? – JustinKSU Jan 15 '19 at 16:33
  • 2
    `@BeforeClass` is only possible on a `static` method, in which I could not make use of `@MockBean private DatabaseService`, so that's not an option. – membersound Jan 15 '19 at 16:36
  • I'd better replace method annotated with `@PostConstruct` with constructor annotated with `@Autowired` and use constructor injection instead of properties injection – Ivan Jan 15 '19 at 18:08
  • Possible duplicate of [Unit testing: Call @PostConstruct after defining mocked behaviour](https://stackoverflow.com/questions/38175822/unit-testing-call-postconstruct-after-defining-mocked-behaviour) – JustinKSU Jan 15 '19 at 18:33

1 Answers1

0

The root cause seems to be that @MockBean is applied after any spring initialization and post processing has finished. Thus also after @PostConstruct.

Therefore I move the "name"-caching into the database service itself as follows:

@Service
public class DatabaseService {
    private Set<String> names;

    public Set<String> findAllByName() {
        if (names == null) {
            names = dao.executeSql(...);
        }
        return names;
    }
}


public class MyService {
    public void run() {
        //now when this is called, the mock is already available
        System.out.println(db.findAllByName());
    }
}

Maybe it's bad code trying to cache the content inside MyService, that's why junit and mockito force that to be implemented differently?

membersound
  • 81,582
  • 193
  • 585
  • 1,120