Search code examples
javaspring-bootjunitjdbctemplate

Spring Boot/@JDBCTest - No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available


Bit of a Spring Boot rookie here, so appreciate any help!

I've built a Spring Boot application with JDBCTemplate that runs and functions normally with no errors or exceptions.

I have produced a test class and want to use @JdbcTest to test my Dao objects. However, every time I run a test, I get java.lang.IllegalStateException: Failed to load ApplicationContext. There seems to be an issue with my Controller classes. This IllegalStateException is caused by:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'assetApiController' defined in file [/PATH/TO/FILE]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

My test class:

@JdbcTest
@Sql({"schema.sql", "test-data.sql"})
class AssetApiControllerTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    AssetDao assetDao;

    @Test
    void delete() throws DataAccessException {
        assetDao.setJdbcTemplate(jdbcTemplate);

        assetDao.deleteByPk(new AssetKey(1), null);
        assertEquals(0, assetDao.selectAll(null).size());
    }
}

My Dao:

@Repository("assetDao")
public class AssetDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    public void deleteByPk (AssetKey assetKey, Connection con) throws DataAccessException {

        jdbcTemplate.update("DELETE FROM Asset WHERE id = ? ", assetKey.getId());
    }
   }

My controller class:

@Controller
public class AssetApiController implements AssetApi {

    private static final Logger log = LoggerFactory.getLogger(AssetApiController.class);

    private final ObjectMapper objectMapper;

    private final HttpServletRequest request;

    @Autowired
    private AssetDao assetDao;

    @org.springframework.beans.factory.annotation.Autowired
    public AssetApiController(ObjectMapper objectMapper, HttpServletRequest request) {
        this.objectMapper = objectMapper;
        this.request = request;
    }

    @Override
    public ResponseEntity<Void> delete(@RequestBody AssetKey assetKey) {
        try{
            assetDao.deleteByPk(assetKey, null);
        } catch (ApplicationException e) {
            log.warn(e.getFormattedMessage(), e);
            return new ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<Void>(HttpStatus.OK);
    }

 }                       

My JacksonConfig:

@Configuration
public class JacksonConfiguration {

  @Bean
  @ConditionalOnMissingBean(ThreeTenModule.class)
  ThreeTenModule threeTenModule() {
    ThreeTenModule module = new ThreeTenModule();
    module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT);
    module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME);
    module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME);
    return module;
  }

}

Two more points that could help diagnose this:

  1. I followed this answer: https://stackoverflow.com/a/32842962/11853066 . And it fixed the problem, but then I got an equivalent error for HttpServletRequest: No qualifying bean found for dependency [javax.servlet.http.HttpServletRequest]. So the underlying issue had not been addressed.

  2. When I try and produce tests for an entity other than Asset (e.g. 'Supplier' with SupplierController, SupplierDao), I get the same error: Error creating bean with name 'assetApiController' defined in file. This must be because assetApiController is the first controller to be scanned because of alphabetical order?


Solution

  • Hi :) What spring boot version do you have? Overall seems like a generic application context configuration issue. I would rather refactor a bit before starting with the test. It is very good to use the @Autowired constructors in Spring Boot. It will make your life much easier when you want to test.

    1. Refactor the DAO class.
    @Repository("assetDao")
    public class AssetDao {
    
        private final JdbcTemplate jdbcTemplate;
        
        //handy you can put also validation here if all is good
        //we will use it later in the test
        @Autowired
        public AssetDao (JdbcTemplate jdbcTemplate){
            jdbcTemplate = jdbcTemplate;
        }
        
        public void deleteByPk (AssetKey assetKey, Connection con) throws DataAccessException 
        {
            jdbcTemplate.update("DELETE FROM Asset WHERE id = ? ", assetKey.getId());
        }
    }
    
    1. Now the test... seems you test the DAO via the Controller? What happens is the whole application context wants to load togather with your dao. You want to @Autowire. You have to init the spring context inside your test etc...there fore the errors you get. If you want to focus on the DAO you could do:
    @JdbcTest
    @Sql({"schema.sql", "test-data.sql"})
    class AssetDaoTest {
        private AssetDao assetDao;
        private JdbcTemplate template;
        
        @Before
        public void setup() {
            template = new JdbcTemplate();
            assetDao = new AssetDao(template);
        }
    
        @Test
        void delete() throws DataAccessException {
            assetDao.deleteByPk(new AssetKey(1), null);
            assertEquals(0, assetDao.selectAll(null).size());
        }
    }
    

    Give it a try, did not run it on my PC yet.