【问题标题】:Junit test cases for Rest APIs connecting to DB连接到数据库的 Rest API 的 Junit 测试用例
【发布时间】:2015-08-26 05:46:56
【问题描述】:

我有一个 JAVA REST 服务,它与数据库交互,进行一些操作并返回数据。

我正在尝试为这些 API 编写测试用例。

我正在尝试使用以下链接作为参考来实现这一点。

http://www.developer.com/java/other/article.php/10936_3882311_2/Mockito-Java-Unit-Testing-with-Mock-Objects.htm

在这里,对数据库的调用被抑制,dto 被模拟为虚构的值。

是否有另一种方法可以让我们在不与 db 对话的情况下运行查询,(内存中的 db 可能是?)

任何代码示例或参考都会有很大帮助。

【问题讨论】:

    标签: java rest unit-testing junit automated-tests


    【解决方案1】:

    对于纯 HashMap 解决方案,类似这样的方法可以工作,但您会失去对 SQL 查询功能的访问权限(除非您也模拟查询)。

    public class MockDatabase<T> {
    
    protected Map<Serializable, T> fakeDatabase = Maps.newHashMap();
    
    private final CustomRepository<T,Serializable> repository;
    
    private Validator validator;
    
    public void setValidator(Validator validator) {
        this.validator = validator;
    }
    
    public static <T extends CustomRepository> T mock(Class<T> classToMock, Validator validator) {
    
        T repository = Mockito.mock(classToMock);
        MockDatabase md = new MockDatabase<T>(repository, validator);
    
        return repository;
    }
    
    
    public <ID extends Serializable> MockDatabase(CustomRepository<T, ID> repository, Validator validator){
        this.repository = (CustomRepository<T, Serializable>) repository;
        this.validator = validator;
    
    
        reset(repository);
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                fakeDatabase.clear();
                return null;
            }
        }).when(repository).deleteAll();
    
        when(repository.save((T) anyObject())).thenAnswer(new Answer<T>() {
            @Override
            public T answer(InvocationOnMock invocation) throws Throwable {
                return saveOrSaveAndFlush(invocation);
            }
        });
    
    
        when(repository.getReference((ID)anyObject())).thenAnswer(new Answer<T>() {
            @Override
            public T answer(InvocationOnMock invocation) throws Throwable {
                return fakeDatabase.get(invocation.getArguments()[0]);
            }
        });
    
       when(repository.findOne((ID)anyObject())).thenAnswer(new Answer<T>() {
           @Override
           public T answer(InvocationOnMock invocation) throws Throwable {
               return fakeDatabase.get(invocation.getArguments()[0]);
           }
       });
    
        doAnswer(new Answer<T>() {
            @Override
            public T answer(InvocationOnMock invocation) throws Throwable {
                return fakeDatabase.remove(ReflectionTestUtils.invokeGetterMethod(invocation.getArguments()[0], "getId"));
            }
        }).when(repository).delete((T)anyObject());
    
    
        doAnswer(new Answer<ID>() {
            @Override
            public ID answer(InvocationOnMock invocation) throws Throwable {
               fakeDatabase.remove(((ID)invocation.getArguments()[0]));
                return null;
            }
        }).when(repository).delete((ID)anyObject());
    
    
        when(repository.saveAndFlush((T) anyObject())).thenAnswer(new Answer<T>() {
            @Override
            public T answer(InvocationOnMock invocation) throws Throwable {
                return saveOrSaveAndFlush(invocation);
            }
    
    
        });
    
        when(repository.exists((ID)anyObject())).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                return fakeDatabase.containsKey(invocation.getArguments()[0]);
            }
        });
    
    
        when(repository.merge(anyObject())).thenAnswer(new Answer<T>() {
            @Override
            public T answer(InvocationOnMock invocation) throws Throwable {
                return (T) invocation.getArguments()[0];
            }
        });
    
    
        when(repository.findAll()).thenAnswer(new Answer<List<T>>() {
            @Override
            public List<T> answer(InvocationOnMock invocation) throws Throwable {
                return Lists.newLinkedList(fakeDatabase.values());
            }
        });
    
        customMethods();
    
    }
    
    private T saveOrSaveAndFlush(InvocationOnMock invocation) throws NoSuchMethodException {
        Object[] args = invocation.getArguments();
        Serializable id = (Serializable) ReflectionTestUtils.getField(args[0], "id");
        if (id == null) {
            Class<?> returnType = args[0].getClass().getMethod("getId").getReturnType();
    
            if (returnType.equals(Long.class)) {
                id = (Long) new Random().nextLong();
            } else if (returnType.equals(Integer.class)) {
                id = (Integer) new Random().nextInt();
            }
            ReflectionTestUtils.setField(args[0], "id", id);
        }
    
        Set<ConstraintViolation<T>> validations = validator.validate((T)args[0]);
        if (!validations.isEmpty()){
            throw new IllegalStateException("Object failed validations (it would also fail on a db): "+validations);
        }
        for (Method method: args[0].getClass().getDeclaredMethods()){
            if (method.isAnnotationPresent(Basic.class)){
                Annotation a = method.getAnnotation(Basic.class);
                if (!(boolean) AnnotationUtils.getValue(method.getAnnotation(Basic.class), "optional")){
                    if (ReflectionTestUtils.invokeGetterMethod(args[0], method.getName()) == null){
                       throw new IllegalStateException(args[0].getClass().getSimpleName()+"."+method.getName() + " returned null, but marked with @Basic(optional=false) - it would also fail on a db: "+validations);
    
                    }
                }
            }
        }
        fakeDatabase.put(id, (T) args[0]);
    
        return (T) args[0];
    }
    
    public void customMethods() {
        // override here if you want
    }
    

    }

    如果你有 @Entity 注释的 POJO,那么说 hibernate 库你可以要求它导出到 HSQLDB 脚本然后使用它。例如,您通过以下方式导出:

    Configuration configuration = new Configuration();
            try {
                classes().forEach(cl -> {
                    configuration.addAnnotatedClass(cl);
                });
                configuration.setProperty("hibernate.dialect", HSQLCustomDialect.class.getName());
                SchemaExport schemaExport = new SchemaExport(configuration);
                schemaExport.setOutputFile("someFileName.sql");
                schemaExport.setFormat(false);
                schemaExport.setDelimiter(";");
                schemaExport.execute(true, false, false, true);
    

    然后您将使用 spring 为您插入该 SQL 脚本:

    @ActiveProfiles("test")
    @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)
     @SqlGroup({
        @Sql(statements = "DROP SCHEMA PUBLIC CASCADE"),
        @Sql(scripts = "classpath:yourGeneratedSQL.sql"),
    })
    

    公共类 DAOIntegrationTest {

    【讨论】:

      【解决方案2】:

      HSQLDB 是我熟悉的内存数据库之一。此处显示的示例针对与 hibernate 和 JPA 一起使用的 HSQLDB。 http://uaihebert.com/tdd-with-hsqldb-jpa-and-hibernate/

      但是,我认为询问您为什么更愿意连接到内存数据库而不是在您的情况下模拟数据库会很有用? 它归结为您要实现的测试单元/集成的范围。 你想在休息层测试什么操作逻辑?嘲笑就足够了。 您是否尝试测试其余部分如何处理数据访问行为,例如 db 错误处理等,而不是在内存中可能会稍微好一些。 您正在测试的东西是否依赖于数据设置/测试数据设置,在这种情况下,内存数据库可能更接近,因为您可以使用相同/相似的 sql 创建来测试内存数据库。

      【讨论】:

      • 谢谢!但是我们将 jdbc 用于 dao 层。可以将其包含在测试范围中吗?
      • 是的,HSQLDB 也可以与 jdbc 一起使用。您只需在 hsqldb.org/web/usagelinks.html 上找到更具体的示例,例如 HSQLDB jdbc 示例。您肯定希望它在测试范围内。我想你首先必须为你的 dao 创建一个适当的抽象,在测试版本中它是一个 HSQLDBDAO 实现,而真正的将是你的 DBDAO(例如 oracle/sybase 等)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-06
      • 1970-01-01
      • 2012-01-17
      • 2017-12-29
      相关资源
      最近更新 更多