def test_ObservableSet_with_any_operation_after_dispose_throws_DisposedException(
            self):
        # arrange
        obs = self.scheduler.create_observer()
        new_dict = ObservableDict({6: 'Polar', 5: 'Dingo'})

        new_dict.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act & assert
        new_dict.dispose()
        with self.assertRaises(DisposedException):
            new_dict.setdefault(6)
class ObservableDictMutationTests(unittest.TestCase):
    def setUp(self):
        self.od = ObservableDict({
            1: 'Crash',
            2: 'Coco',
            3: 'Pura',
            4: 'Tiny Tiger'
        })
        self.empty = ObservableDict()

    def test_ObservableDict_get_method_returns_appropriately(self):
        # arrange & act
        va11 = self.od.get(1)
        val2 = self.od.get(5)
        va13 = self.od.get(5, 'Dingo')

        # assert
        self.assertEqual(len(self.od), 4)
        self.assertTrue(va11 == 'Crash')
        self.assertIsNone(val2)
        self.assertIsNotNone(va13)
        self.assertEqual(va13, 'Dingo')

    def test_ObservableDict_get_with_index_access(self):
        # arrange
        val1 = self.od[1]

        # act & assert
        self.assertEqual(val1, 'Crash')

        with self.assertRaises(KeyError):
            # noinspection PyUnusedLocal
            value = self.od[5]

    def test_ObservableDict_del_a_value_or_entire_dict(self):
        # arrange & act
        del self.od[2]

        # assert
        self.assertEqual(len(self.od), 3)
        self.assertIsNone(self.od.get(2))

    def test_ObservableDict_items_returns_items_as_key_value_pair(self):
        # arrange & act
        items = self.od.items()

        # assert
        self.assertIsNotNone(items)
        self.assertEqual(len(items), 4)

    def test_ObservableDict_del_deletes_entire_dictionary_and_calls_dispose(
            self):
        # arrange
        local_dict = ObservableDict({1: 'hello', 2: 'world'})

        # act
        del local_dict

        # assert
        with self.assertRaises(UnboundLocalError):
            self.assertIsNone(local_dict)

    def test_ObservableDict_pop_removes_the_items_with_key_specified_and_returns_its_value(
            self):
        # arrange & act
        val1 = self.od.pop(4)
        val2 = self.od.pop(5, 'Dr. Neo Cortex')

        # assert
        self.assertEqual(len(self.od), 3)
        self.assertEqual(val1, 'Tiny Tiger')
        self.assertEqual(val2, 'Dr. Neo Cortex')

    def test_ObservableDict_popitem_removes_random_value(self):
        # arrange & act
        val1 = self.od.popitem()

        # assert
        self.assertIsNotNone(val1)
        self.assertEqual(len(self.od), 3)

    def test_ObservableDict_set_default_does_nothing_when_key_exists(self):
        # arrange & act
        val1 = self.od.setdefault(1)
        val2 = self.od.setdefault(2, 'Dingo')

        # assert
        self.assertIsNotNone(val1)
        self.assertEqual(val1, 'Crash')
        self.assertIsNotNone(val2)
        self.assertEqual(val2, 'Coco')

    def test_ObservableDict_set_default_creates_new_key_if_not_exists(self):
        # arrange & act
        val1 = self.od.setdefault(5, 'Dingo')
        val2 = self.od.setdefault(6)

        # assert
        self.assertIsNotNone(val1)
        self.assertEqual(val1, 'Dingo')
        self.assertIsNone(val2)

    def test_ObservableDict_fromkeys_creates_new_ObservableDict(self):
        # arrange & act
        new_dict = ObservableDict.fromkeys({1, 2, 3, 4, 5})

        # assert
        self.assertIsNotNone(new_dict)
        self.assertTrue(isinstance(new_dict, ObservableDict))
        self.assertEqual(len(new_dict), 5)

    def test_ObservableDict_clear_removes_all_items_in_dictionary(self):
        # arrange & act
        self.od.clear()

        # assert
        self.assertIsNotNone(self.od)
        self.assertEqual(len(self.od), 0)

    def tearDown(self):
        self.od.dispose()
        self.empty.dispose()
class RxNotificationObservableDictTest(unittest.TestCase):
    def setUp(self):
        self.od = ObservableDict({1: 'Crash', 2: 'Coco', 3: 'Pura', 4: 'Tiny'})
        self.scheduler = TestScheduler()

    def test_ObservableDict_elements_deleted_with_del_keyword(self):
        # arrange
        obs = self.scheduler.create_observer()
        expected_message = [on_next(0, 2)]

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        del self.od[2]

        # assert
        self.assertEqual(obs.messages, expected_message)
        self.assertEqual(len(self.od), 3)
        self.assertIsNone(self.od.get(2))

    def test_ObservableDict_del_keyword_with_invalid_key(self):
        # arrange
        obs = self.scheduler.create_observer()
        expected_error = [on_error(0, KeyError(5))]

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        del self.od[5]

        # assert
        self.assertEqual(obs.messages, expected_error)

    def test_ObservableDict_pop_with_invalid_key(self):
        # arrange
        obs = self.scheduler.create_observer()
        expected_message = [on_next(0, 'Coco'), on_error(0, KeyError(5))]

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        self.od.pop(2)
        self.od.pop(5)
        self.od.pop(3)

        # assert
        self.assertEqual(obs.messages, expected_message)
        self.assertEqual(len(self.od),
                         2)  # TODO: Re-visit here to see if this is valid. ???

    def test_ObservableDict_popitem_removes_arbitrary_value(self):
        # arrange
        obs = self.scheduler.create_observer()

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        self.od.popitem()
        self.od.popitem()

        # assert
        self.assertEqual(len(self.od), 2)
        self.assertEqual(len(obs.messages), 2)

    def test_ObservableDict_popitem_in_empty_dict_pushes_KeyError_in_on_error(
            self):
        # arrange
        obs = self.scheduler.create_observer()
        empty = ObservableDict()
        expected_error = [
            on_error(0, KeyError('popitem(): dictionary is empty'))
        ]

        empty.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        empty.popitem()

        # assert
        self.assertEqual(obs.messages, expected_error)

    def test_ObservableDict_test_default_adds_key_if_not_exists(self):
        # arrange
        obs = self.scheduler.create_observer()

        self.od.when_collection_changes() \
            .subscribe(obs)

        # act
        self.od.setdefault(5, 'Dingo')
        self.od.setdefault(6)

        # assert
        self.assertEqual(len(self.od), 6)
        self.assertEqual(len(obs.messages), 2)
        self.assertEqual(obs.messages[0].value.value.Items, 'Dingo')
        self.assertEqual(obs.messages[0].value.value.Action,
                         CollectionChangeAction.ADD)
        self.assertEqual(obs.messages[1].value.value.Items, ())
        self.assertEqual(obs.messages[1].value.value.Action,
                         CollectionChangeAction.ADD)

    def test_ObservableDict_test_default_does_nothing_key_if_exists(self):
        # arrange
        obs = self.scheduler.create_observer()
        expected_message = []

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        self.od.setdefault(2, 'Dingo')
        self.od.setdefault(1)

        # assert
        self.assertEqual(len(self.od), 4)
        self.assertEqual(len(obs.messages), 0)
        self.assertEqual(obs.messages, expected_message)

    def test_ObservableDict_update_method_updates_existing_dictionary(self):
        # arrange
        obs = self.scheduler.create_observer()
        expected_values = ['Polar', 'Dingo']

        self.od.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act
        self.od.update(ObservableDict({2: 'Polar', 5: 'Dingo'}))

        # assert
        self.assertEqual(len(self.od), 5)
        self.assertEqual(self.od.get(2), 'Polar')
        self.assertTrue(isinstance(obs.messages[0].value.value,
                                   ObservableDict))
        self.assertEqual(list(obs.messages[0].value.value._dict.values()),
                         expected_values)

    def test_ObservableDict_clear_removes_all_items_and_publish_clear_event(
            self):
        # arrange
        obs = self.scheduler.create_observer()

        self.od.when_collection_changes() \
            .subscribe(obs)

        # act
        self.od.clear()

        # assert
        self.assertEqual(len(self.od), 0)
        self.assertEqual(len(obs.messages), 1)
        self.assertEqual(obs.messages[0].value.value.Items, ())
        self.assertEqual(obs.messages[0].value.value.Action,
                         CollectionChangeAction.CLEAR)

    def test_ObservableSet_with_any_operation_after_dispose_throws_DisposedException(
            self):
        # arrange
        obs = self.scheduler.create_observer()
        new_dict = ObservableDict({6: 'Polar', 5: 'Dingo'})

        new_dict.when_collection_changes() \
            .map(lambda x: x.Items) \
            .subscribe(obs)

        # act & assert
        new_dict.dispose()
        with self.assertRaises(DisposedException):
            new_dict.setdefault(6)

    def tearDown(self):
        self.od.dispose()