java - 可观察/列表或属性 : exception thrown in listener not "seen" by unit test

标签 java javafx junit properties observablelist

场景:一个具有一些可观察字段的类(无论是简单的属性还是可观察列表,都无关紧要),并具有该字段的监听器。如果客户端代码尝试将可观察值更改为任何无效值,则监听器将引发异常。

测试此行为时,异常会按预期抛出(显示在控制台上),但测试方法看不到它。这是一个预期异常会失败的测试。

感觉我错过了一些明显的东西:

  • 为什么会发生这种情况?
  • 我的设置/期望有什么问题吗?
  • 如何解决:要么让测试通过,要么更改设置或其他什么?

示例:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;

/**
 * Trying to dig into issue with expected exceptions. They are shown
 * on the console, but not seen by test runner.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
@RunWith(JUnit4.class)

public class ExceptionFailingTest {

    @Test (expected = IllegalStateException.class)
    public void testExceptionFromListChangeListener() {
        ListOwner owner = new ListOwner();
        owner.getObjects().clear();
    }

    /**
     * Exception thrown in a ChangeListener is not seen by test.
     */
    @Test (expected = IllegalStateException.class)
    public void testExceptionFromPropertyListener() {
        ListOwner owner = new ListOwner();
        owner.getProperty().setValue(null);
    }

    public static class ListOwner {

        private ObservableList objects;
        private Property property;

        public ListOwner() {
            objects = FXCollections.observableArrayList("some", "things", "in", "me");
            objects.addListener((ListChangeListener)c -> objectsChanged(c));
            property = new SimpleObjectProperty(this, "property", "initial");
            property.addListener((src, ov, nv) -> propertyChanged(ov));
        }

        public Property getProperty() {
            return property;
        }

        protected void propertyChanged(Object ov) {
            if (property.getValue() == null)
                throw new IllegalStateException("property must not be empty");
        }

        public ObservableList getObjects() {
            return objects;
        }

        protected void objectsChanged(Change c) {
            if (c.getList().isEmpty())
                throw new IllegalStateException("objects must not be empty");
        }
    }
}

最佳答案

正如评论中提到的,监听器抛出的异常本质上是被抑制的。这实际上看起来是一个合理的 API 设计选择(尽管有些文档会很好):当调用更改监听器(或列表更改监听器)时,属性或列表的值已更改。因此,如果您有多个监听器,则一个监听器抛出异常将有效地否决观察更改的其他监听器,但不会否决更改本身。也很难看出如何实现(JPA 风格)“回滚”:如果第二个监听器抛出异常,则必须以某种方式“未通知”第一个监听器。

所以我认为否决更改的方法根本不是通过监听器,而是通过子类化适当的属性/列表类并覆盖修改可观察对象的适当方法。

例如,要拥有一个永远不为空的可观察列表,您可以执行以下操作[警告:并非旨在提高生产质量,只是方法的说明]:

import java.util.List;

import javafx.collections.ModifiableObservableListBase;

public class NonEmptyObservableList<E> extends ModifiableObservableListBase<E> {

    private final List<E> source ;

    public NonEmptyObservableList(List<E> source) {
        if (source.isEmpty()) {
            throw new IllegalStateException("List cannot be empty");
        }
        this.source = source ;
    }


    @Override
    public E get(int index) {
        return source.get(index);
    }

    @Override
    public int size() {
        return source.size();
    }

    @Override
    protected void doAdd(int index, E element) {
        source.add(index, element);
    }

    @Override
    protected E doSet(int index, E element) {
        return source.set(index, element);
    }

    @Override
    protected E doRemove(int index) {
        if (size() <= 1) {
            throw new IllegalStateException("List cannot be empty");
        }
        return source.remove(index);
    }

}

请注意,调用clear()在此列表中,将有点任意地保留“最后一个”元素。这是一个单元测试:

import java.util.ArrayList;
import java.util.Arrays;

import org.junit.Test;

import org.junit.Assert;

public class NonEmptyObservableListTest {

    @Test (expected = IllegalStateException.class)
    public void testExceptionFromListChangeListener() {
        NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
        list.clear();
    }


    @Test
    public void testSizeOnClear() {
        NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
        try {
            list.clear();
        } catch (Exception e) {
            // squash exception to test list size...
        }
        Assert.assertSame("List size is not 1", list.size(), 1);
    }
}

您也可以考虑覆盖 clear() “原子否决”更改:

@Override
public void clear() {
    throw new IllegalStateException("List cannot be empty");
}

这将使列表在调用 clear() 时保持不变(而不是在其中保留一个元素),尽管很难涵盖所有可能性( list.subList(0, list.size()).clear() ...)。

尝试创建一个通用的可否决的可观察列表(使用 Predicate<List<E>> 来确定是否允许更改)会很有趣,但以有效的方式这样做将非常具有挑战性(并且留作练习)读者)。

关于java - 可观察/列表或属性 : exception thrown in listener not "seen" by unit test,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40242090/

相关文章:

java - 使用 querydsl 模拟数据库查询 - 可选问题

JavaFX 模块对 VM 不可见

java - EMMA 与 Junit,我应该使用检测类还是 java 类进行测试?

java - H2 createTcpServer() 不创建服务器?

java - JSF 2.0 方法调用,参数来自 dataGrid 的 var

java - 如何使用 CSS 更改 JavaFX FileChooser 的外观?

java - 从 DB 检索图像到 imageView

java - 如何在使用 Serenity BDD 的同时为 Chrome 设置日志记录首选项?

java - FileNET P8 5.2.1 FP2 - 编辑文档创建权限

java - Chess GUI - 在调用函数之前获得 2 次用户点击 - Java