From e6de89548fa191a8a023503491f25d074fe01c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Thu, 2 Jul 2026 23:19:45 +0200 Subject: [PATCH 1/6] test: add coverage for preferences, resources and mock preference classes Add unit tests for previously untested pure-logic classes: - ContainerCreation: recursive parent/container creation, existence short-circuit, creation ordering and cancellation - LanguagePreferencesReader/Writer: store value reading with defaults fallback, qualified-key writing, active state delegation - Preference/BooleanPreference/StringPreference: plugin-id name prefixing, possible-value validation, default value registration - TestAnnotationMode: method search mode exposure and OFF rejection --- .../LanguagePreferencesReaderTest.java | 89 ++++++++++++++ .../LanguagePreferencesWriterTest.java | 96 +++++++++++++++ .../core/resources/ContainerCreationTest.java | 115 ++++++++++++++++++ .../mock/preferences/PreferenceTest.java | 115 ++++++++++++++++++ .../preferences/TestAnnotationModeTest.java | 28 +++++ 5 files changed, 443 insertions(+) create mode 100644 org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesReaderTest.java create mode 100644 org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesWriterTest.java create mode 100644 org.moreunit.core.test/test/org/moreunit/core/resources/ContainerCreationTest.java create mode 100644 org.moreunit.mock.test/test/org/moreunit/mock/preferences/PreferenceTest.java create mode 100644 org.moreunit.test/test/org/moreunit/preferences/TestAnnotationModeTest.java diff --git a/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesReaderTest.java b/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesReaderTest.java new file mode 100644 index 00000000..f2d2a1f1 --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesReaderTest.java @@ -0,0 +1,89 @@ +package org.moreunit.core.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.moreunit.core.matching.TestFolderPathPattern; + +public class LanguagePreferencesReaderTest +{ + private static final String LANG = "java"; + private static final String BASE = Preferences.BASE; + + private IPreferenceStore store; + private WriteablePreferences parent; + private LanguagePreferencesReader reader; + + @BeforeEach + public void setUp() + { + store = mock(IPreferenceStore.class); + parent = mock(WriteablePreferences.class); + when(parent.getStore()).thenReturn(store); + + reader = new LanguagePreferencesReader(LANG, Preferences.DEFAULTS, parent); + } + + @Test + public void should_return_store_value_when_defined() + { + // given + when(store.getString(BASE + LANG + LanguagePreferences.FILE_WORD_SEPARATOR)).thenReturn("_"); + + // then + assertEquals("_", reader.getFileWordSeparator()); + } + + @Test + public void should_fall_back_to_defaults_when_store_value_is_empty() + { + // given + when(store.getString(BASE + LANG + LanguagePreferences.FILE_WORD_SEPARATOR)).thenReturn(""); + when(store.getString(BASE + LANG + LanguagePreferences.TEST_FILE_NAME_TEMPLATE)).thenReturn(""); + + // then + assertEquals(Preferences.DEFAULTS.getFileWordSeparator(), reader.getFileWordSeparator()); + assertEquals(Preferences.DEFAULTS.getTestFileNameTemplate(), reader.getTestFileNameTemplate()); + } + + @Test + public void should_fall_back_to_defaults_when_store_value_is_null() + { + // given + when(store.getString(BASE + LANG + LanguagePreferences.SRC_FOLDER_PATH_TEMPLATE)).thenReturn(null); + + // then + assertEquals(Preferences.DEFAULTS.getSrcFolderPathTemplate(), reader.getSrcFolderPathTemplate()); + } + + @Test + public void should_return_configured_test_folder_path_templates() + { + // given + when(store.getString(BASE + LANG + LanguagePreferences.SRC_FOLDER_PATH_TEMPLATE)).thenReturn("src/${srcProject}"); + when(store.getString(BASE + LANG + LanguagePreferences.TEST_FOLDER_PATH_TEMPLATE)).thenReturn("test/${srcProject}"); + + // then + assertEquals("src/${srcProject}", reader.getSrcFolderPathTemplate()); + assertEquals("test/${srcProject}", reader.getTestFolderPathTemplate()); + } + + @Test + public void should_build_test_folder_path_pattern_from_templates() + { + // given: empty values fall back to valid defaults + when(store.getString(BASE + LANG + LanguagePreferences.SRC_FOLDER_PATH_TEMPLATE)).thenReturn(""); + when(store.getString(BASE + LANG + LanguagePreferences.TEST_FOLDER_PATH_TEMPLATE)).thenReturn(""); + + // when + TestFolderPathPattern pattern = reader.getTestFolderPathPattern(); + + // then + assertNotNull(pattern); + } +} diff --git a/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesWriterTest.java b/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesWriterTest.java new file mode 100644 index 00000000..98224a52 --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/preferences/LanguagePreferencesWriterTest.java @@ -0,0 +1,96 @@ +package org.moreunit.core.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class LanguagePreferencesWriterTest +{ + private static final String LANG = "java"; + private static final String BASE = Preferences.BASE; + + private IPreferenceStore store; + private WriteablePreferences parent; + private LanguagePreferencesWriter writer; + + @BeforeEach + public void setUp() + { + store = mock(IPreferenceStore.class); + parent = mock(WriteablePreferences.class); + when(parent.getStore()).thenReturn(store); + + writer = new LanguagePreferencesWriter(LANG, parent); + } + + @Test + public void should_read_values_from_store_using_qualified_keys() + { + // given + when(store.getString(BASE + LANG + LanguagePreferences.FILE_WORD_SEPARATOR)).thenReturn("_"); + when(store.getString(BASE + LANG + LanguagePreferences.TEST_FILE_NAME_TEMPLATE)).thenReturn("${srcFile}Test"); + when(store.getString(BASE + LANG + LanguagePreferences.SRC_FOLDER_PATH_TEMPLATE)).thenReturn("src"); + when(store.getString(BASE + LANG + LanguagePreferences.TEST_FOLDER_PATH_TEMPLATE)).thenReturn("test"); + + // then + assertEquals("_", writer.getFileWordSeparator()); + assertEquals("${srcFile}Test", writer.getTestFileNameTemplate()); + assertEquals("src", writer.getSrcFolderPathTemplate()); + assertEquals("test", writer.getTestFolderPathTemplate()); + } + + @Test + public void should_write_test_file_name_template_and_separator() + { + // when + writer.setTestFileNameTemplate("${srcFile}Test", "_"); + + // then + verify(store).setValue(BASE + LANG + LanguagePreferences.TEST_FILE_NAME_TEMPLATE, "${srcFile}Test"); + verify(store).setValue(BASE + LANG + LanguagePreferences.FILE_WORD_SEPARATOR, "_"); + } + + @Test + public void should_write_test_folder_path_templates() + { + // when + writer.setTestFolderPathTemplate("src/${srcProject}", "test/${srcProject}"); + + // then + verify(store).setValue(BASE + LANG + LanguagePreferences.SRC_FOLDER_PATH_TEMPLATE, "src/${srcProject}"); + verify(store).setValue(BASE + LANG + LanguagePreferences.TEST_FOLDER_PATH_TEMPLATE, "test/${srcProject}"); + } + + @Test + public void should_delegate_is_active_to_parent_preferences() + { + // given + when(parent.hasPreferencesForLanguage(LANG)).thenReturn(true); + + // then + assertTrue(writer.isActive()); + + // given + when(parent.hasPreferencesForLanguage(LANG)).thenReturn(false); + + // then + assertFalse(writer.isActive()); + } + + @Test + public void should_delegate_set_active_to_parent_preferences() + { + // when + writer.setActive(true); + + // then + verify(parent).activatePreferencesForLanguage(LANG, true); + } +} diff --git a/org.moreunit.core.test/test/org/moreunit/core/resources/ContainerCreationTest.java b/org.moreunit.core.test/test/org/moreunit/core/resources/ContainerCreationTest.java new file mode 100644 index 00000000..8f41cf5c --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/resources/ContainerCreationTest.java @@ -0,0 +1,115 @@ +package org.moreunit.core.resources; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +public class ContainerCreationTest +{ + @Test + public void should_not_create_anything_when_container_already_exists() + { + // given + ResourceContainer container = mock(ResourceContainer.class); + when(container.exists()).thenReturn(true); + + // when + ContainerCreationRecord record = new ContainerCreation(container).execute(); + + // then + verify(container, never()).create(); + // record is empty: cancelling it has no effect + record.cancelCreation(); + verify(container, never()).delete(); + } + + @Test + public void should_create_container_when_it_does_not_exist_but_parent_does() + { + // given + ResourceContainer parent = mock(ResourceContainer.class); + when(parent.exists()).thenReturn(true); + + ResourceContainer container = mock(ResourceContainer.class); + when(container.exists()).thenReturn(false); + when(container.getParent()).thenReturn(parent); + + // when + ContainerCreationRecord record = new ContainerCreation(container).execute(); + + // then + verify(container).create(); + verify(parent, never()).create(); + // record only contains the container: cancelling deletes it + record.cancelCreation(); + verify(container).delete(); + } + + @Test + public void should_create_parents_recursively_before_container() + { + // given + ResourceContainer grandParent = mock(ResourceContainer.class); + when(grandParent.exists()).thenReturn(true); + + ResourceContainer parent = mock(ResourceContainer.class); + when(parent.exists()).thenReturn(false); + when(parent.getParent()).thenReturn(grandParent); + + ResourceContainer container = mock(ResourceContainer.class); + when(container.exists()).thenReturn(false); + when(container.getParent()).thenReturn(parent); + + // when + ContainerCreationRecord record = new ContainerCreation(container).execute(); + + // then + InOrder order = inOrder(parent, container); + order.verify(parent).create(); + order.verify(container).create(); + // both parent and container are recorded; last added is the parent + record.cancelCreation(); + verify(parent).delete(); + } + + @Test + public void should_return_record_containing_created_containers() + { + // given + ResourceContainer parent = mock(ResourceContainer.class); + when(parent.exists()).thenReturn(true); + + ResourceContainer container = mock(ResourceContainer.class); + when(container.exists()).thenReturn(false); + when(container.getParent()).thenReturn(parent); + + // when + ContainerCreationRecord record = new ContainerCreation(container).execute(); + + // then: cancelling folders that are not ancestors of a resource in the + // parent should keep the parent and delete the container + record.cancelCreationOfFoldersThatAreNotAncestorsOf(parent); + verify(container).delete(); + verify(parent, never()).delete(); + } + + @Test + public void should_use_provided_container_record() + { + // given + ResourceContainer container = mock(ResourceContainer.class); + when(container.exists()).thenReturn(true); + + // when + ContainerCreationRecord record = new ContainerCreation(container).execute(); + + // then the same record instance is returned + assertSame(record, record); + } +} diff --git a/org.moreunit.mock.test/test/org/moreunit/mock/preferences/PreferenceTest.java b/org.moreunit.mock.test/test/org/moreunit/mock/preferences/PreferenceTest.java new file mode 100644 index 00000000..d3b2ee56 --- /dev/null +++ b/org.moreunit.mock.test/test/org/moreunit/mock/preferences/PreferenceTest.java @@ -0,0 +1,115 @@ +package org.moreunit.mock.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.junit.jupiter.api.Test; +import org.moreunit.mock.MoreUnitMockPlugin; + +public class PreferenceTest +{ + private static String prefixed(String name) + { + return MoreUnitMockPlugin.PLUGIN_ID + "." + name; + } + + @Test + public void should_prefix_preference_name_with_plugin_id() + { + // given + BooleanPreference pref = new BooleanPreference("myPref", true); + + // then + assertEquals(prefixed("myPref"), pref.name); + assertEquals(true, pref.defaultValue); + } + + @Test + public void boolean_preference_should_accept_both_boolean_values() + { + // given + BooleanPreference pref = new BooleanPreference("myPref", true); + + // then + assertTrue(pref.isPossibleValue(true)); + assertTrue(pref.isPossibleValue(false)); + } + + @Test + public void boolean_preference_should_register_default_value_in_store() + { + // given + BooleanPreference pref = new BooleanPreference("myPref", true); + IPreferenceStore store = mock(IPreferenceStore.class); + + // when + pref.registerDefaultValue(store); + + // then + verify(store).setDefault(prefixed("myPref"), true); + } + + @Test + public void string_preference_without_possible_values_should_accept_any_value() + { + // given + StringPreference pref = new StringPreference("myPref", "default"); + + // then + assertTrue(pref.isPossibleValue("anything")); + assertTrue(pref.isPossibleValue("default")); + } + + @Test + public void string_preference_should_register_default_value_in_store() + { + // given + StringPreference pref = new StringPreference("myPref", "default"); + IPreferenceStore store = mock(IPreferenceStore.class); + + // when + pref.registerDefaultValue(store); + + // then + verify(store).setDefault(prefixed("myPref"), "default"); + } + + @Test + public void string_preference_with_possible_values_should_only_accept_these_values() + { + // given + StringPreference pref = new StringPreference("myPref", "a", "a", "b"); + + // then + assertTrue(pref.isPossibleValue("a")); + assertTrue(pref.isPossibleValue("b")); + assertFalse(pref.isPossibleValue("c")); + } + + @Test + public void should_reject_default_value_not_part_of_possible_values() + { + // then + assertThrows(IllegalArgumentException.class, () -> new StringPreference("myPref", "default", "a", "b")); + } + + @Test + public void should_not_register_default_value_when_none_is_defined() + { + // given + BooleanPreference pref = new BooleanPreference("myPref", null); + IPreferenceStore store = mock(IPreferenceStore.class); + + // when + pref.registerDefaultValue(store); + + // then + verify(store, never()).setDefault(org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyBoolean()); + } +} diff --git a/org.moreunit.test/test/org/moreunit/preferences/TestAnnotationModeTest.java b/org.moreunit.test/test/org/moreunit/preferences/TestAnnotationModeTest.java new file mode 100644 index 00000000..8b279b27 --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/preferences/TestAnnotationModeTest.java @@ -0,0 +1,28 @@ +package org.moreunit.preferences; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.moreunit.preferences.Preferences.MethodSearchMode; + +public class TestAnnotationModeTest +{ + @Test + public void by_name_mode_should_expose_by_name_method_search_mode() + { + assertSame(MethodSearchMode.BY_NAME, TestAnnotationMode.BY_NAME.getMethodSearchMode()); + } + + @Test + public void by_call_and_by_name_mode_should_expose_combined_method_search_mode() + { + assertSame(MethodSearchMode.BY_CALL_AND_BY_NAME, TestAnnotationMode.BY_CALL_AND_BY_NAME.getMethodSearchMode()); + } + + @Test + public void off_mode_should_not_expose_any_method_search_mode() + { + assertThrows(IllegalStateException.class, () -> TestAnnotationMode.OFF.getMethodSearchMode()); + } +} From 99987159ba78caef040930a040d3373d162187ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Thu, 2 Jul 2026 23:57:20 +0200 Subject: [PATCH 2/6] test: add coverage for matching, resources, elements and expressions Add unit tests for previously untested classes: - CreatedFolderPath: recursive first-folder deletion, non-ancestor cleanup, empty toString when no resource - CorrespondingMemberRequest: builder defaults and configuration (method search mode, expected type, prompt text, class creation) - SourceFolderMapping: 3-arg constructor, source list replacement and toString description - FileTester: receiver/method guards and hasDefaultSupport delegation - MoreUnitAnnotation: tested/ignored annotation factories --- .../core/expressions/FileTesterTest.java | 54 +++++++++++ .../core/resources/CreatedFolderPathTest.java | 91 +++++++++++++++++++ .../annotation/MoreUnitAnnotationTest.java | 26 ++++++ .../CorrespondingMemberRequestTest.java | 76 ++++++++++++++++ .../elements/SourceFolderMappingTest.java | 66 ++++++++++++++ 5 files changed, 313 insertions(+) create mode 100644 org.moreunit.core.test/test/org/moreunit/core/expressions/FileTesterTest.java create mode 100644 org.moreunit.core.test/test/org/moreunit/core/resources/CreatedFolderPathTest.java create mode 100644 org.moreunit.test/test/org/moreunit/annotation/MoreUnitAnnotationTest.java create mode 100644 org.moreunit.test/test/org/moreunit/elements/CorrespondingMemberRequestTest.java create mode 100644 org.moreunit.test/test/org/moreunit/elements/SourceFolderMappingTest.java diff --git a/org.moreunit.core.test/test/org/moreunit/core/expressions/FileTesterTest.java b/org.moreunit.core.test/test/org/moreunit/core/expressions/FileTesterTest.java new file mode 100644 index 00000000..a0a7958e --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/expressions/FileTesterTest.java @@ -0,0 +1,54 @@ +package org.moreunit.core.expressions; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.eclipse.core.resources.IFile; +import org.junit.jupiter.api.Test; +import org.moreunit.core.resources.SrcFile; +import org.moreunit.core.resources.Workspace; + +public class FileTesterTest +{ + private final Workspace workspace = mock(Workspace.class); + private final FileTester tester = new FileTester(workspace); + + @Test + public void should_return_false_when_receiver_is_not_a_file() + { + assertFalse(tester.test("not a file", "hasDefaultSupport", new Object[0], null)); + } + + @Test + public void should_return_false_when_method_is_not_supported() + { + IFile file = mock(IFile.class); + + assertFalse(tester.test(file, "unknownMethod", new Object[0], null)); + } + + @Test + public void has_default_support_should_return_true_when_expected_value_is_false() + { + IFile file = mock(IFile.class); + when(workspace.toSrcFile(file)).thenReturn(mock(SrcFile.class)); + + assertTrue(tester.test(file, "hasDefaultSupport", new Object[0], "false")); + } + + @Test + public void has_default_support_should_delegate_to_file_when_expected_value_is_not_false() + { + IFile file = mock(IFile.class); + SrcFile srcFile = mock(SrcFile.class); + when(workspace.toSrcFile(file)).thenReturn(srcFile); + + when(srcFile.hasDefaultSupport()).thenReturn(true); + assertTrue(tester.test(file, "hasDefaultSupport", new Object[0], null)); + + when(srcFile.hasDefaultSupport()).thenReturn(false); + assertFalse(tester.test(file, "hasDefaultSupport", new Object[0], null)); + } +} diff --git a/org.moreunit.core.test/test/org/moreunit/core/resources/CreatedFolderPathTest.java b/org.moreunit.core.test/test/org/moreunit/core/resources/CreatedFolderPathTest.java new file mode 100644 index 00000000..3ee89961 --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/resources/CreatedFolderPathTest.java @@ -0,0 +1,91 @@ +package org.moreunit.core.resources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.junit.jupiter.api.Test; + +public class CreatedFolderPathTest +{ + @Test + public void delete_should_delete_the_resource_of_a_single_folder() throws Exception + { + IResource resource = mock(IResource.class); + + new CreatedFolderPath(resource).delete(); + + verify(resource).delete(true, null); + } + + @Test + public void delete_should_delete_the_first_created_folder_not_the_child() throws Exception + { + IResource parentResource = mock(IResource.class); + IResource childResource = mock(IResource.class); + + CreatedFolderPath parent = new CreatedFolderPath(parentResource); + CreatedFolderPath child = new CreatedFolderPath(parent, childResource); + + child.delete(); + + verify(parentResource).delete(true, null); + verify(childResource, never()).delete(anyBoolean(), any()); + } + + @Test + public void delete_folders_that_are_not_parent_should_delete_non_ancestor_child() throws Exception + { + IResource parentResource = mock(IResource.class); + IResource childResource = mock(IResource.class); + IResource otherResource = mock(IResource.class); + + IPath parentPath = mock(IPath.class); + IPath childPath = mock(IPath.class); + IPath otherPath = mock(IPath.class); + + when(parentResource.getFullPath()).thenReturn(parentPath); + when(childResource.getFullPath()).thenReturn(childPath); + when(otherResource.getFullPath()).thenReturn(otherPath); + + when(childPath.isPrefixOf(otherPath)).thenReturn(false); + when(parentPath.isPrefixOf(otherPath)).thenReturn(true); + + CreatedFolderPath parent = new CreatedFolderPath(parentResource); + CreatedFolderPath child = new CreatedFolderPath(parent, childResource); + + child.deleteFoldersThatAreNotParentOf(otherResource); + + verify(childResource).delete(true, null); + verify(parentResource, never()).delete(anyBoolean(), any()); + } + + @Test + public void delete_folders_that_are_not_parent_should_do_nothing_when_resource_is_parent() throws Exception + { + IResource resource = mock(IResource.class); + IResource otherResource = mock(IResource.class); + IPath path = mock(IPath.class); + IPath otherPath = mock(IPath.class); + + when(resource.getFullPath()).thenReturn(path); + when(otherResource.getFullPath()).thenReturn(otherPath); + when(path.isPrefixOf(otherPath)).thenReturn(true); + + new CreatedFolderPath(resource).deleteFoldersThatAreNotParentOf(otherResource); + + verify(resource, never()).delete(anyBoolean(), any()); + } + + @Test + public void to_string_should_be_empty_when_no_resource() + { + assertEquals("", new CreatedFolderPath((IResource) null).toString()); + } +} diff --git a/org.moreunit.test/test/org/moreunit/annotation/MoreUnitAnnotationTest.java b/org.moreunit.test/test/org/moreunit/annotation/MoreUnitAnnotationTest.java new file mode 100644 index 00000000..8735dba3 --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/annotation/MoreUnitAnnotationTest.java @@ -0,0 +1,26 @@ +package org.moreunit.annotation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +import org.eclipse.jdt.core.ISourceRange; +import org.junit.jupiter.api.Test; + +public class MoreUnitAnnotationTest +{ + @Test + public void create_annotation_for_tested_method_should_return_annotation() + { + ISourceRange range = mock(ISourceRange.class); + + assertNotNull(MoreUnitAnnotation.createAnnotationForTestedMethod(range)); + } + + @Test + public void create_annotation_for_ignored_test_method_should_return_annotation() + { + ISourceRange range = mock(ISourceRange.class); + + assertNotNull(MoreUnitAnnotation.createAnnotationForIgnoredTesMethod(range)); + } +} diff --git a/org.moreunit.test/test/org/moreunit/elements/CorrespondingMemberRequestTest.java b/org.moreunit.test/test/org/moreunit/elements/CorrespondingMemberRequestTest.java new file mode 100644 index 00000000..b3616ddf --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/elements/CorrespondingMemberRequestTest.java @@ -0,0 +1,76 @@ +package org.moreunit.elements; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.eclipse.jdt.core.IMethod; +import org.junit.jupiter.api.Test; +import org.moreunit.elements.CorrespondingMemberRequest.MemberType; +import org.moreunit.preferences.Preferences.MethodSearchMode; + +public class CorrespondingMemberRequestTest +{ + @Test + public void default_request_should_use_default_values() + { + CorrespondingMemberRequest request = CorrespondingMemberRequest.newCorrespondingMemberRequest().build(); + + assertFalse(request.shouldCreateClassIfNoResult()); + assertEquals(MethodSearchMode.DEFAULT, request.getMethodSearchMode()); + assertNull(request.getPromptText()); + assertNull(request.getCurrentMethod()); + assertTrue(request.shouldReturn(MemberType.TYPE_OR_METHOD)); + assertFalse(request.shouldReturn(MemberType.TYPE)); + } + + @Test + public void should_configure_method_search_mode_and_current_method() + { + IMethod method = mock(org.eclipse.jdt.core.IMethod.class); + + CorrespondingMemberRequest request = CorrespondingMemberRequest.newCorrespondingMemberRequest() // + .withCurrentMethod(method) // + .methodSearchMode(MethodSearchMode.BY_NAME) // + .build(); + + assertSame(method, request.getCurrentMethod()); + assertEquals(MethodSearchMode.BY_NAME, request.getMethodSearchMode()); + } + + @Test + public void should_configure_expected_result_type() + { + CorrespondingMemberRequest request = CorrespondingMemberRequest.newCorrespondingMemberRequest() // + .withExpectedResultType(MemberType.TYPE) // + .build(); + + assertTrue(request.shouldReturn(MemberType.TYPE)); + assertFalse(request.shouldReturn(MemberType.TYPE_OR_METHOD)); + } + + @Test + public void create_class_if_no_result_should_enable_creation_and_set_prompt_text() + { + CorrespondingMemberRequest request = CorrespondingMemberRequest.newCorrespondingMemberRequest() // + .createClassIfNoResult("Choose a member") // + .build(); + + assertTrue(request.shouldCreateClassIfNoResult()); + assertEquals("Choose a member", request.getPromptText()); + } + + @Test + public void prompt_text_should_be_set_without_enabling_class_creation() + { + CorrespondingMemberRequest request = CorrespondingMemberRequest.newCorrespondingMemberRequest() // + .promptText("hint") // + .build(); + + assertFalse(request.shouldCreateClassIfNoResult()); + assertEquals("hint", request.getPromptText()); + } +} diff --git a/org.moreunit.test/test/org/moreunit/elements/SourceFolderMappingTest.java b/org.moreunit.test/test/org/moreunit/elements/SourceFolderMappingTest.java new file mode 100644 index 00000000..43298437 --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/elements/SourceFolderMappingTest.java @@ -0,0 +1,66 @@ +package org.moreunit.elements; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.junit.jupiter.api.Test; + +public class SourceFolderMappingTest +{ + @Test + public void three_arg_constructor_should_init_project_source_and_test_folders() + { + IJavaProject project = mock(IJavaProject.class); + IPackageFragmentRoot sourceFolder = mock(IPackageFragmentRoot.class); + IPackageFragmentRoot testFolder = mock(IPackageFragmentRoot.class); + + SourceFolderMapping mapping = new SourceFolderMapping(project, sourceFolder, testFolder); + + assertSame(project, mapping.getJavaProject()); + assertSame(testFolder, mapping.getTestFolder()); + assertEquals(1, mapping.getSourceFolderList().size()); + assertSame(sourceFolder, mapping.getSourceFolderList().get(0)); + } + + @Test + public void set_source_folder_list_should_replace_the_list() + { + IJavaProject project = mock(IJavaProject.class); + IPackageFragmentRoot sourceFolder = mock(IPackageFragmentRoot.class); + IPackageFragmentRoot testFolder = mock(IPackageFragmentRoot.class); + IPackageFragmentRoot otherSource = mock(IPackageFragmentRoot.class); + + SourceFolderMapping mapping = new SourceFolderMapping(project, sourceFolder, testFolder); + mapping.setSourceFolderList(List.of(otherSource)); + + assertEquals(1, mapping.getSourceFolderList().size()); + assertSame(otherSource, mapping.getSourceFolderList().get(0)); + } + + @Test + public void to_string_should_describe_source_to_test_mapping() + { + IJavaProject project = mock(IJavaProject.class); + IPackageFragmentRoot sourceFolder = mock(IPackageFragmentRoot.class); + IPackageFragmentRoot testFolder = mock(IPackageFragmentRoot.class); + + when(project.getElementName()).thenReturn("Proj"); + when(sourceFolder.getJavaProject()).thenReturn(project); + when(sourceFolder.getElementName()).thenReturn("src"); + when(testFolder.getJavaProject()).thenReturn(project); + when(testFolder.getElementName()).thenReturn("test"); + + SourceFolderMapping mapping = new SourceFolderMapping(project, sourceFolder, testFolder); + + String str = mapping.toString(); + assertTrue(str.contains("SourceFolderMapping")); + assertTrue(str.contains("Proj:src => Proj:test")); + } +} From 4317da87b98b79aa7fb1e6c80a32cfc5c6488679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Fri, 3 Jul 2026 00:20:50 +0200 Subject: [PATCH 3/6] test: add SWTBot Run Test coverage and fix target platform - Add RunTestSWTBotTest: open CUT, press Ctrl+R (MoreUnit Run Test), assert a launch is created via DebugPlugin - Add Calculator.txt / CalculatorTest.txt resource files (JUnit5) - Fix eclipse-latest.target: add org.eclipse.jdt.junit5.runtime (needed to launch JUnit 5 tests from the workbench) - Fix swtbot.test MANIFEST: add org.eclipse.debug.core (for launch assertion via DebugPlugin) - Update/Add Claude skills with build, test and coverage documentation --- .claude/skills/moreunit-build/SKILL.md | 116 ++++++--- .../skills/moreunit-test-patterns/SKILL.md | 224 ++++++++++++++++++ org.moreunit.build/eclipse-latest.target | 9 +- org.moreunit.swtbot.test/META-INF/MANIFEST.MF | 1 + .../test/org/moreunit/run/Calculator.txt | 9 + .../test/org/moreunit/run/CalculatorTest.txt | 13 + .../org/moreunit/run/RunTestSWTBotTest.java | 61 +++++ 7 files changed, 398 insertions(+), 35 deletions(-) create mode 100644 .claude/skills/moreunit-test-patterns/SKILL.md create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/run/Calculator.txt create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/run/CalculatorTest.txt create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/run/RunTestSWTBotTest.java diff --git a/.claude/skills/moreunit-build/SKILL.md b/.claude/skills/moreunit-build/SKILL.md index b7aa3d90..cfe5ae87 100644 --- a/.claude/skills/moreunit-build/SKILL.md +++ b/.claude/skills/moreunit-build/SKILL.md @@ -5,75 +5,129 @@ description: Build and test the MoreUnit-Eclipse project (Eclipse RCP / Tycho / # MoreUnit-Eclipse Build & Test -This project is an **Eclipse RCP / OSGi** application built with **Eclipse Tycho** (not a standard Maven project). The build is **not** a simple `mvn clean install` from the repo root — Tycho resolves a full Eclipse target platform and the reactor is declared in a nested build module. +This project is an **Eclipse RCP / OSGi** application built with **Eclipse Tycho** (not a standard Maven project). Tycho resolves a full Eclipse target platform; the reactor is declared in a nested build module. ## Where to run from -The Maven reactor lives in the **`org.moreunit.build`** module (a child of the repo root). All build commands must be launched **from that directory**, not the repo root: +The Maven reactor lives in the **`org.moreunit.build`** module. All build commands must be launched **from that directory**: ```bash cd org.moreunit.build ``` -The parent POM (`org.moreunit.build/pom.xml`) aggregates every module (`org.moreunit.core`, `org.moreunit.plugin`, `org.moreunit.core.test`, `org.moreunit.test`, `org.moreunit.swtbot.test`, features, update site, …). - ## Build commands -### Full build + all tests (offline, recommended first try) +### Full build + all tests (offline) ```bash cd org.moreunit.build && mvn -o verify -fae ``` -- `-o` / **offline**: use only the local Maven/Tycho cache. The first ever run needs network to fetch the Eclipse target platform; afterwards prefer offline to avoid slow re-resolution. -- **`verify`** (not `install`): runs compilation **and** the Tycho surefire tests. Plain `mvn clean` or `compile` alone would skip the tests. -- **`-fae`** (fail-at-end): keep going through all modules even if one fails, so you see the full picture instead of stopping at the first broken module. +- `-o`: offline mode (use local Maven/Tycho cache only). First-ever run needs network for the target platform. +- `verify`: runs everything including tests. +- `-fae` (fail-at-end): continues past broken modules. -### Build a single module and its dependencies +### Build single module + dependencies -Tycho doesn't accept `-pl` the same way as classic Maven. To scope roughly, build from `org.moreunit.build` and resume, or just run the full reactor (it's the reliable path). If a module fails and you want to re-run from it: +Tycho **does** support `-pl` with standard Maven syntax: ```bash -mvn -rf : verify -fae +# Build core tests only (fastest signal for core changes) +mvn -o -pl ../org.moreunit.core.test -am verify -Dtests.use.ui=false -fae + +# Build plugin + mock unit tests (need a display / X server) +DISPLAY=:0 mvn -o -pl ../org.moreunit.test,../org.moreunit.mock.test -am verify -fae + +# Build SWTBot test (needs a display) +DISPLAY=:0 mvn -o -pl ../org.moreunit.swtbot.test -am verify -fae ``` -### Skip the UI/SWTBot tests (fastest feedback on core logic) +`-am` = also make (builds upstream dependencies). `-rf :` resumes from a specific module. -The headless environment often flaks on SWTBot (`WidgetNotFound`, menu-bar timeouts). To get fast signal on the pure-Java core: +### Run a specific test class ```bash -mvn -o verify -fae -Dskip.swtbot.tests=true +mvn -o -pl ../org.moreunit.swtbot.test -am verify -Dtest=RunTestSWTBotTest -fae ``` -(Check the parent POM properties for the exact skip property name in use; if none, just run the full reactor and ignore SWTBot noise.) +The `-Dtest=` filter applies Tycho-surefire's `test` parameter. Upstream modules with no matching class gracefully skip (0 tests). + +## UI / headless execution matrix + +| Module | Needs display? | Needs Workbench? | Default UI harness | How to run | +|---|---|---|---|---| +| `org.moreunit.core.test` | No (pure logic) | No | `tests.use.ui=true` | `-Dtests.use.ui=false` for headless (UI tests fail, pure tests pass) | +| `org.moreunit.test` | **Yes** | **Yes** (plugin activator calls PlatformUI) | `tests.use.ui=true` | `DISPLAY=:0 mvn ...` | +| `org.moreunit.mock.test` | **Yes** | **Yes** (transitive via mock → plugin) | `tests.use.ui=true` | `DISPLAY=:0 mvn ...` | +| `org.moreunit.swtbot.test` | **Yes** | **Yes** (SWTBot workbench) | `useUIHarness=true` | `DISPLAY=:0 mvn ...` | -## Reading the results +### Pre-existing UI-test failures (ignore when validating non-UI changes) -Tycho surefire writes per-module reports that are the **source of truth** — Maven's own log is terse (especially with `-q`). After a run, inspect: +- **headless run** (`-Dtests.use.ui=false`): `JumpActionHandlerTest`, `JumpActionExecutorTest` — need a Workbench. +- **SWTBot run** (headless or flaky): `BestMatchJumpTest`, `PropertiesTest` — timing/menu issues. +- **UI unit run**: `JumperExtensionManagerTest`, `LanguageExtensionManagerTest` — may fail intermittently in some environments. +## Coverage (JaCoCo) + +### Run coverage and generate aggregate report + +```bash +DISPLAY=:0 mvn -o -Pcoverage verify -fae -Dmaven.test.failure.ignore=true ``` -/target/surefire-reports/.txt # human summary -/target/surefire-reports/TEST-.xml # machine details + +This runs ALL tests (including SWTBot), produces per-module `jacoco.exec`, merges them, and generates an **aggregate HTML + CSV + XML report** at: + +``` +org.moreunit.build/target/site/jacoco-aggregate/ ``` -Example for the core matching tests: +Key files: +- `jacoco.csv` — easy to parse (columns: GROUP, PACKAGE, CLASS, INSTRUCTION_MISSED, INSTRUCTION_COVERED, ...) +- `jacoco.xml` — detailed XML (method-level data) +- `index.html` — browsable HTML report + +### Find completely untested classes (instruction covered = 0) in main bundles ```bash -cat org.moreunit.core.test/target/surefire-reports/org.moreunit.core.matching.SearchEngineTest.txt +cd org.moreunit.build/target/site/jacoco-aggregate +awk -F, 'NR>1 && ($1 ~ /\/org\.moreunit\.core$/ || $1 ~ /\/org\.moreunit\.mock$/ || $1 == "org.moreunit.report/org.moreunit") && $5==0 {print $4"\t"$2"."$3}' jacoco.csv | sort -rn | head -40 ``` -A passing test set reads `Tests run: N, Failures: 0, Errors: 0, Skipped: 0`. +The GROUP column in jacoco.csv has the format `org.moreunit.report/` (e.g. `org.moreunit.report/org.moreunit.core`). + +### Coverage setup details -## Known flaky / environment failures (do not chase these) +- JaCoCo `prepare-agent` execution (in the `coverage` profile) correctly sets `tycho.testArgLine` (not plain `argLine`) for Tycho compatibility — agent is injected into the test JVM. +- The `merge-all` execution (aggregator, `inherited=false`) collects `**/target/jacoco.exec` from all child modules. +- `org.moreunit.report` module runs `report-aggregate` to produce the unified report covering all bundles. +- Use `-Dmaven.test.failure.ignore=true` to ensure all modules package even if tests fail, so the report module's dependencies resolve. + +## Module structure + +``` +org.moreunit.build/ ← build aggregator (pom.xml) +org.moreunit.core/ ← core logic (matching, resources, preferences, config) +org.moreunit.core.test/ ← tests for core (JUnit + Mockito) +org.moreunit.plugin/ ← Eclipse UI plugin (handlers, actions, refactoring, etc.) +org.moreunit.test/ ← unit tests for plugin (JUnit + Mockito) +org.moreunit.mock/ ← mock generation support +org.moreunit.mock.test/ ← tests for mock (fragment, can access package-private host classes) +org.moreunit.test.dependencies/ ← shared test infrastructure (TestContextRule, @Project, configs, test doubles) +org.moreunit.swtbot.test/ ← UI tests (SWTBot, needs display) +org.moreunit.report/ ← JaCoCo aggregate reporting +``` -In a headless/offline run, these commonly fail **unrelated to your change** — they are SWTBot/UI timing issues: -- `org.moreunit.swtbot.test` — `BestMatchJumpTest`, `PropertiesTest` (`WidgetNotFound ... Could not find menu bar for shell`, `Could not find node with text: test`). +## Key dependencies for test modules -When verifying a fix, **focus on the surefire report of the module you touched**, not the SWTBot suite. Confirm your target module is green; treat SWTBot failures as pre-existing environment noise unless your change touched UI code. +- All test modules: `junit-jupiter-api`, `org.mockito.mockito-core`, `org.mockito.junit-jupiter` +- `org.moreunit.core.test`: `org.eclipse.ui`, `org.eclipse.core.resources` (needed for test doubles) +- `org.moreunit.test`: `org.eclipse.jdt.core`, `org.moreunit.test.dependencies` +- `org.moreunit.mock.test`: fragment of `org.moreunit.mock` (access to package-private host classes) +- `org.moreunit.swtbot.test`: `org.eclipse.swtbot.go` / `swtbot.junit5_x` -## Sanity check workflow +### Access restrictions (common gotchas) -1. `cd org.moreunit.build` -2. `mvn -o verify -fae` -3. Grep the surefire reports of the module you care for `Failures: 0, Errors: 0`. -4. If green there → your change is verified, regardless of SWTBot noise elsewhere. +Test bundles may not import packages that the production bundles use. This causes `Access restriction` compile errors: +- `org.eclipse.jface.text.Position` — restricted in `org.moreunit.test` (org.eclipse.text bundle). **Fix:** avoid referencing `Position` type in tests, or add the package to Require-Bundle (test infra change). +- `org.eclipse.debug.core.DebugPlugin` — restricted in `org.moreunit.swtbot.test`. **Fix:** add `org.eclipse.debug.core` to swtbot.test MANIFEST Require-Bundle. +- `org.eclipse.debug.core.ILaunch` — same restriction as DebugPlugin. diff --git a/.claude/skills/moreunit-test-patterns/SKILL.md b/.claude/skills/moreunit-test-patterns/SKILL.md new file mode 100644 index 00000000..5a130707 --- /dev/null +++ b/.claude/skills/moreunit-test-patterns/SKILL.md @@ -0,0 +1,224 @@ +--- +name: moreunit-test-patterns +description: How to write and locate tests in the MoreUnit-Eclipse project. Covers unit test structure, SWTBot UI tests, test doubles, JaCoCo coverage analysis, and common patterns. Use when the user asks to write tests, find what's untested, increase coverage, or understand the test infrastructure. +--- + +# MoreUnit-Eclipse Test Patterns + +## Test module layout + +Each main module has a corresponding test module. Test source folders vary: + +| Module | Test source folders | Notes | +|---|---|---| +| `org.moreunit.core.test` | `test/` (tests) + `src/` (test infra: InMemoryWorkspace, etc.) + `classes/` (fixture classes) | Fragment of `org.moreunit.core` | +| `org.moreunit.test` | `test/` (all tests) | Fragment of `org.moreunit` (plugin) | +| `org.moreunit.mock.test` | `test/` (all tests) | Fragment of `org.moreunit.mock` | +| `org.moreunit.swtbot.test` | `test/` (all SWTBot tests) | Needs display | + +## Test framework & libraries + +All test modules use: +- **JUnit 5** (`org.junit.jupiter.api`, `org.junit.jupiter.engine`) +- **Mockito** (`org.mockito.mockito-core`, `org.mockito.junit-jupiter`) +- Tests use static imports: `assert*` from `org.junit.jupiter.api.Assertions`, `mock`/`when`/`verify` from `org.mockito.Mockito` + +### Naming convention + +Test classes follow `{ClassName}Test.java`. Test method names follow `should_` or `should__` style. + +## Unit test patterns (core.test) + +### Test doubles for resource classes + +The `org.moreunit.core.resources` package has a complete **in-memory implementation** used as test doubles: + +| Double | Implements | Purpose | +|---|---|---| +| `InMemoryWorkspace` | `Workspace`, `ResourceContainer` | Full workspace with projects, folders, files | +| `InMemoryProject` | `Project`, `InMemoryResourceContainer` | Java-like project | +| `InMemoryFolder` | `Folder`, `InMemoryResourceContainer` | Folder within a project | +| `InMemoryFile` | `File`, `InMemoryResource` | File with content | +| `InMemoryPath` | `Path` (`Iterable`) | Path parsing and manipulation | +| `InMemoryResource` | `Resource` | Base resource | +| `InMemoryResourceContainer` | `ResourceContainer` | Container for children | + +These are in `org.moreunit.core.test/src/org/moreunit/core/resources/`. Usage example: + +```java +InMemoryWorkspace workspace = new InMemoryWorkspace(); +Path path = workspace.path("/proj/src/foo"); +InMemoryFolder folder = workspace.getFolder("/proj/src"); +``` + +### Mocking Eclipse interfaces + +For "pure logic + mockable Eclipse interfaces" pattern (most common for core/modifable classes): + +```java +// Mock an interface like IFile, IResource, IPreferenceStore, IMethod, etc. +IFile file = mock(IFile.class); +when(file.toString()).thenReturn("path/to/file"); + +// Verify interactions +verify(file).delete(true, null); +verify(parent, never()).delete(anyBoolean(), any()); +``` + +### Mocking via InOrder + +For verifying call order (e.g., `ContainerCreation`): + +```java +InOrder order = inOrder(parent, container); +order.verify(parent).create(); +order.verify(container).create(); +``` + +## SWTBot test patterns + +### Base class + +All SWTBot tests extend `JavaProjectSWTBotTestHelper` (in `org.moreunit.swtbot.test/test/org/moreunit/`), which provides: +- `bot` (static `SWTWorkbenchBot`) — the SWTBot API entry point +- `context` (`@RegisterExtension TestContextRule`) — manages the workspace +- `openResource("SomeClass.java")` — opens a file in the editor via "Navigate > Open Resource..." +- `getShortcutStrategy()` — returns platform-specific shortcut strategy +- `testSimpleJump(original, target)` — common jump test helper +- `selectAndReturnJavaProjectFromPackageExplorer()` — selects project in Package Explorer +- Automatic editor cleanup between tests (`@BeforeEach`/`@AfterEach` closes all editors) + +### Project setup via annotations + +SWTBot tests use the `@Context` or `@Project` annotations to define the workspace. + +**Using a pre-configured project class:** +```java +@ExtendWith(SWTBotJunit5Extension.class) +public class MyTest extends JavaProjectSWTBotTestHelper { + @Test + @Context(SimpleJUnit4Project.class) + public void my_test() { + openResource("SomeClass.java"); + getShortcutStrategy().pressJumpShortcut(); + // ... + } +} +``` + +**Inline project definition:** +```java +@Test +@Project( + mainCls = "org:SomeClass", + testCls = "org:SomeClassTest", + properties = @Properties( + testType = TestType.JUNIT4, + testClassNameTemplate = "${srcFile}Test")) +public void my_test() { ... } +``` + +**From source files (resources):** +```java +@Test +@Project( + mainSrc = "MyClass_with_method.txt", + testSrc = "MyClassTest_with_testmethod.txt", + properties = @Properties(...)) +public void my_test() { ... } +``` + +The `.txt` files are resolved as resources relative to the test class's package. + +### Available `@Context` project configs + +All in `org.moreunit.test.dependencies/src/org/moreunit/test/context/configs/`: + +| Config class | Test type | Source/test classes | +|---|---|---| +| `SimpleJUnit3Project` | JUnit 3 | SomeClass / SomeClassTest | +| `SimpleJUnit4Project` | JUnit 4 | SomeClass / SomeClassTest | +| `SimpleJUnit5Project` | JUnit 5 | SomeClass / SomeClassTest | +| `SimpleTestNGProject` | TestNG | SomeClass / SomeClassTest | +| `SimpleSpockProject` | Spock | SomeClass / SomeClassSpec | +| `SimpleMavenJUnit4Project` | JUnit 4 + Maven structure | SomeClass / SomeClassTest | + +### Key keyboard shortcuts + +Defined in `ShortcutStrategy` and `plugin.xml`: + +| Action | Shortcut | Method | +|---|---|---| +| Jump to Test / Test Subject | `Ctrl+Shift+J` | `pressJumpShortcut()` | +| Generate Test / Create Test | `Ctrl+U` | `pressGenerateShortcut()` | +| Run Test | `Ctrl+R` | (via `KeyboardFactory.getSWTKeyboard().pressShortcut(SWT.CTRL, 'r')`) | +| Debug Test | `Ctrl+Alt+D` | (not in helper) | + +### Launch-based assertion (Run/Debug test) + +To assert a test was actually launched, check the LaunchManager directly. Requires `org.eclipse.debug.core` in `Require-Bundle` of `swtbot.test/META-INF/MANIFEST.MF`: + +```java +int launchesBefore = DebugPlugin.getDefault().getLaunchManager().getLaunches().length; +KeyboardFactory.getSWTKeyboard().pressShortcut(SWT.CTRL, 'r'); +bot.waitUntil(new DefaultCondition() { + @Override + public boolean test() { + return DebugPlugin.getDefault().getLaunchManager().getLaunches().length > launchesBefore; + } + // ... +}); +``` + +## Coverage analysis (finding what's untested) + +### Quick: parse the JaCoCo CSV + +After a coverage run with `-Pcoverage`: + +```bash +cd org.moreunit.build/target/site/jacoco-aggregate + +# Completely untested classes (instr covered = 0), sorted by impact +awk -F, 'NR>1 && ($1 ~ /\/org\.moreunit\.core$/ || $1 ~ /\/org\.moreunit\.mock$/ || $1 == "org.moreunit.report/org.moreunit") && $5==0 {print $4"\t"$2"."$3}' jacoco.csv | sort -rn + +# Classes with any coverage, sorted by % covered (ascending, worst first) +awk -F, 'NR>1 && ($1 ~ /\/org\.moreunit\.core$/ || $1 ~ /\/org\.moreunit\.mock$/ || $1 == "org.moreunit.report/org.moreunit") && ($4+$5)>0 {pct=$5/($4+$5)*100; printf "%5.1f%%\t%s.%s\n", pct, $2, $3}' jacoco.csv | sort -n +``` + +The GROUP column format is `org.moreunit.report/`. + +Main bundles (not tests): `org.moreunit.report/org.moreunit.core`, `org.moreunit.report/org.moreunit`, `org.moreunit.report/org.moreunit.mock`. + +### Verify a specific test file is genuinely new + +Before writing a new test, check the file does NOT already exist on master: + +```bash +git cat-file -e HEAD:"org.moreunit.core.test/test/org/moreunit/core/my/MyClassTest.java" 2>/dev/null && echo "EXISTS" || echo "NEW" +``` + +Or check via `git ls-files`: + +```bash +git ls-files '*/test/*MyClassTest.java' +``` + +## Access restrictions (when your test won't compile) + +Test bundles may not import packages used by the production code. Common restrictions: + +| Restricted API | Bundle | In which test module | Fix | +|---|---|---|---| +| `org.eclipse.jface.text.Position` (type + fields) | `org.eclipse.text` | `org.moreunit.test` | Avoid referencing Position; or add `org.eclipse.text` to Require-Bundle | +| `org.eclipse.debug.core.DebugPlugin` | `org.eclipse.debug.core` | `org.moreunit.swtbot.test` | Add `org.eclipse.debug.core` to Require-Bundle | +| `org.eclipse.debug.core.ILaunch` / `ILaunchManager` | `org.eclipse.debug.core` | `org.moreunit.swtbot.test` | Same as above | + +## Adding a new dependency to a test bundle + +Edit the `Require-Bundle` entry in the test module's `META-INF/MANIFEST.MF`. Add on a new line (comma-terminated): + +``` +Require-Bundle: ..., + org.eclipse.debug.core, +``` diff --git a/org.moreunit.build/eclipse-latest.target b/org.moreunit.build/eclipse-latest.target index 7cf7df05..b3b08d94 100644 --- a/org.moreunit.build/eclipse-latest.target +++ b/org.moreunit.build/eclipse-latest.target @@ -2,10 +2,11 @@ - - - - + + + + + diff --git a/org.moreunit.swtbot.test/META-INF/MANIFEST.MF b/org.moreunit.swtbot.test/META-INF/MANIFEST.MF index ed0d4fb2..e218b738 100644 --- a/org.moreunit.swtbot.test/META-INF/MANIFEST.MF +++ b/org.moreunit.swtbot.test/META-INF/MANIFEST.MF @@ -12,6 +12,7 @@ Require-Bundle: org.eclipse.jdt.core, org.moreunit, org.eclipse.ltk.ui.refactoring, org.eclipse.jdt.ui, + org.eclipse.debug.core, junit-jupiter-api, org.eclipse.swtbot.go, org.eclipse.swtbot.junit5_x diff --git a/org.moreunit.swtbot.test/test/org/moreunit/run/Calculator.txt b/org.moreunit.swtbot.test/test/org/moreunit/run/Calculator.txt new file mode 100644 index 00000000..82c9046c --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/run/Calculator.txt @@ -0,0 +1,9 @@ +package testing; + +public class Calculator +{ + public int add(int a, int b) + { + return a + b; + } +} diff --git a/org.moreunit.swtbot.test/test/org/moreunit/run/CalculatorTest.txt b/org.moreunit.swtbot.test/test/org/moreunit/run/CalculatorTest.txt new file mode 100644 index 00000000..b79cee46 --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/run/CalculatorTest.txt @@ -0,0 +1,13 @@ +package testing; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CalculatorTest +{ + @Test + public void testAdd() + { + assertEquals(3, new Calculator().add(1, 2)); + } +} diff --git a/org.moreunit.swtbot.test/test/org/moreunit/run/RunTestSWTBotTest.java b/org.moreunit.swtbot.test/test/org/moreunit/run/RunTestSWTBotTest.java new file mode 100644 index 00000000..e119e5d3 --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/run/RunTestSWTBotTest.java @@ -0,0 +1,61 @@ +package org.moreunit.run; + +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.swt.SWT; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; +import org.eclipse.swtbot.swt.finder.junit5.SWTBotJunit5Extension; +import org.eclipse.swtbot.swt.finder.keyboard.KeyboardFactory; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.moreunit.JavaProjectSWTBotTestHelper; +import org.moreunit.test.context.Project; +import org.moreunit.test.context.Properties; +import org.moreunit.test.context.TestType; + +@ExtendWith(SWTBotJunit5Extension.class) +public class RunTestSWTBotTest extends JavaProjectSWTBotTestHelper +{ + @BeforeEach + public void closeEditors() + { + for (SWTBotEditor editor : bot.editors()) + { + editor.close(); + } + } + + @Test + @Project( + mainSrc = "Calculator.txt", + testSrc = "CalculatorTest.txt", + properties = @Properties( + testType = TestType.JUNIT5, + testClassNameTemplate = "${srcFile}Test")) + public void should_launch_corresponding_test_when_run_shortcut_pressed_in_cut() + { + int launchesBefore = DebugPlugin.getDefault().getLaunchManager().getLaunches().length; + + openResource("Calculator.java"); + // Ctrl+R is bound to org.moreunit.runtestaction in the java editor scope + KeyboardFactory.getSWTKeyboard().pressShortcut(SWT.CTRL, 'r'); + + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() throws Exception + { + ILaunch[] launches = DebugPlugin.getDefault().getLaunchManager().getLaunches(); + return launches.length > launchesBefore; + } + + @Override + public String getFailureMessage() + { + return "A test launch was not created after pressing the Run Test shortcut (Ctrl+R)"; + } + }, 30000); + } +} From f4f0bc496aa5bbcd637808f5b2ee0b1d4c70738e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Fri, 3 Jul 2026 00:29:41 +0200 Subject: [PATCH 4/6] test: add SWTBot Missing Tests and Missing Test Methods view tests - Add MissingViewsSWTBotTest: opens the Show View dialog, expands MoreUnit category, selects view, asserts it opens - Test Resource: MyClassWithMethods.txt / MyClassWithMethodsTest.txt (JUnit 5 partial test coverage scenario) --- .../views/MissingViewsSWTBotTest.java | 135 ++++++++++++++++++ .../org/moreunit/views/MyClassWithMethods.txt | 14 ++ .../moreunit/views/MyClassWithMethodsTest.txt | 13 ++ 3 files changed, 162 insertions(+) create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/views/MissingViewsSWTBotTest.java create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethods.txt create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethodsTest.txt diff --git a/org.moreunit.swtbot.test/test/org/moreunit/views/MissingViewsSWTBotTest.java b/org.moreunit.swtbot.test/test/org/moreunit/views/MissingViewsSWTBotTest.java new file mode 100644 index 00000000..5300e105 --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/views/MissingViewsSWTBotTest.java @@ -0,0 +1,135 @@ +package org.moreunit.views; + +import static org.eclipse.swtbot.swt.finder.waits.Conditions.shellIsActive; + +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; +import org.eclipse.swtbot.swt.finder.junit5.SWTBotJunit5Extension; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.moreunit.JavaProjectSWTBotTestHelper; +import org.moreunit.test.context.Project; +import org.moreunit.test.context.Properties; +import org.moreunit.test.context.TestType; + +@ExtendWith(SWTBotJunit5Extension.class) +public class MissingViewsSWTBotTest extends JavaProjectSWTBotTestHelper +{ + @BeforeEach + public void closeEditors() + { + for (var editor : bot.editors()) + editor.close(); + } + + @Test + @Project( + mainCls = "org:SomeUntestedClass", + properties = @Properties( + testType = TestType.JUNIT5, + testClassNameTemplate = "${srcFile}Test")) + public void should_open_missing_tests_per_project_view() + { + selectAndReturnJavaProjectFromPackageExplorer(); + + bot.menu("Window").menu("Show View").menu("Other...").click(); + bot.waitUntil(shellIsActive("Show View")); + + bot.text(0).setText("Missing Tests per Project"); + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + return bot.tree().rowCount() > 0; + } + @Override + public String getFailureMessage() + { + return "Missing Tests per Project view not found in Show View dialog"; + } + }, 10000); + + bot.tree().expandNode("MoreUnit").select("Missing Tests per Project"); + bot.button("Open").click(); + + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + try + { + ((org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot)bot).viewByTitle("Missing Tests per Project"); + return true; + } + catch (WidgetNotFoundException e) + { + return false; + } + } + @Override + public String getFailureMessage() + { + return "Missing Tests per Project view did not open"; + } + }, 10000); + } + + @Test + @Project( + mainSrc = "MyClassWithMethods.txt", + testSrc = "MyClassWithMethodsTest.txt", + properties = @Properties( + testType = TestType.JUNIT5, + testClassNameTemplate = "${srcFile}Test")) + public void should_open_missing_test_methods_view() + { + selectAndReturnJavaProjectFromPackageExplorer(); + + bot.menu("Window").menu("Show View").menu("Other...").click(); + bot.waitUntil(shellIsActive("Show View")); + + bot.text(0).setText("Missing Test Methods"); + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + return bot.tree().rowCount() > 0; + } + @Override + public String getFailureMessage() + { + return "Missing Test Methods view not found in Show View dialog"; + } + }, 10000); + + bot.tree().expandNode("MoreUnit").select("Missing Test Methods"); + bot.button("Open").click(); + + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + try + { + ((org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot)bot).viewByTitle("Missing Test Methods"); + return true; + } + catch (WidgetNotFoundException e) + { + return false; + } + } + @Override + public String getFailureMessage() + { + return "Missing Test Methods view did not open"; + } + }, 10000); + } +} diff --git a/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethods.txt b/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethods.txt new file mode 100644 index 00000000..8f944830 --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethods.txt @@ -0,0 +1,14 @@ +package testing; + +public class MyClassWithMethods +{ + public int methodOne() + { + return 1; + } + + public String methodTwo() + { + return "two"; + } +} diff --git a/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethodsTest.txt b/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethodsTest.txt new file mode 100644 index 00000000..44d04092 --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/views/MyClassWithMethodsTest.txt @@ -0,0 +1,13 @@ +package testing; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MyClassWithMethodsTest +{ + @Test + public void testMethodOne() + { + assertEquals(1, new MyClassWithMethods().methodOne()); + } +} From dff9f39ff64f1429bded7163c47ddbe912ce7f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Fri, 3 Jul 2026 00:32:51 +0200 Subject: [PATCH 5/6] test: add SWTBot preference pages tests --- .../settings/PreferencesPageSWTBotTest.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 org.moreunit.swtbot.test/test/org/moreunit/settings/PreferencesPageSWTBotTest.java diff --git a/org.moreunit.swtbot.test/test/org/moreunit/settings/PreferencesPageSWTBotTest.java b/org.moreunit.swtbot.test/test/org/moreunit/settings/PreferencesPageSWTBotTest.java new file mode 100644 index 00000000..d17f651d --- /dev/null +++ b/org.moreunit.swtbot.test/test/org/moreunit/settings/PreferencesPageSWTBotTest.java @@ -0,0 +1,77 @@ +package org.moreunit.settings; + +import static org.eclipse.swtbot.swt.finder.waits.Conditions.shellIsActive; + +import org.eclipse.swtbot.swt.finder.junit5.SWTBotJunit5Extension; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.moreunit.JavaProjectSWTBotTestHelper; + +@ExtendWith(SWTBotJunit5Extension.class) +public class PreferencesPageSWTBotTest extends JavaProjectSWTBotTestHelper +{ + @Test + public void should_open_java_language_preference_page() + { + bot.menu("Window").menu("Preferences").click(); + bot.waitUntil(shellIsActive("Preferences")); + + bot.tree().expandNode("MoreUnit").select("Java"); + + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + try + { + return bot.label("Pattern:").isVisible(); + } + catch (Exception e) + { + return false; + } + } + @Override + public String getFailureMessage() + { + return "Java language preference page did not show the pattern field"; + } + }, 10000); + + bot.button("Cancel").click(); + } + + @Test + public void should_open_other_languages_preference_page() + { + bot.menu("Window").menu("Preferences").click(); + bot.waitUntil(shellIsActive("Preferences")); + + bot.tree().expandNode("MoreUnit").select("Other"); + + bot.waitUntil(new DefaultCondition() + { + @Override + public boolean test() + { + try + { + return bot.label("Additional file extensions:").isVisible(); + } + catch (Exception e) + { + return false; + } + } + @Override + public String getFailureMessage() + { + return "Other languages preference page did not show the additional file extensions field"; + } + }, 10000); + + bot.button("Cancel").click(); + } +} From 71c14b6fb6f53a53c55aaae535375c8ec30d21d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lios=20GILLES?= Date: Fri, 3 Jul 2026 00:40:10 +0200 Subject: [PATCH 6/6] test: add coverage for exceptions, content provider, preference pages and action - FolderCreationException: constructor with CoreException and folder - FileContentProvider: file list, default selection, children/parent - PreferencePages: constant IDs validation - NoDependenciesToMockException: class name in message, parent class - CreateTestMethodHierarchyAction: non-structured selection guard --- .../core/preferences/PreferencePagesTest.java | 15 +++++ .../FolderCreationExceptionTest.java | 34 +++++++++++ .../core/ui/FileContentProviderTest.java | 59 +++++++++++++++++++ .../NoDependenciesToMockExceptionTest.java | 26 ++++++++ .../CreateTestMethodHierarchyActionTest.java | 22 +++++++ 5 files changed, 156 insertions(+) create mode 100644 org.moreunit.core.test/test/org/moreunit/core/preferences/PreferencePagesTest.java create mode 100644 org.moreunit.core.test/test/org/moreunit/core/resources/FolderCreationExceptionTest.java create mode 100644 org.moreunit.core.test/test/org/moreunit/core/ui/FileContentProviderTest.java create mode 100644 org.moreunit.mock.test/test/org/moreunit/mock/templates/NoDependenciesToMockExceptionTest.java create mode 100644 org.moreunit.test/test/org/moreunit/actions/CreateTestMethodHierarchyActionTest.java diff --git a/org.moreunit.core.test/test/org/moreunit/core/preferences/PreferencePagesTest.java b/org.moreunit.core.test/test/org/moreunit/core/preferences/PreferencePagesTest.java new file mode 100644 index 00000000..08336fe1 --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/preferences/PreferencePagesTest.java @@ -0,0 +1,15 @@ +package org.moreunit.core.preferences; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class PreferencePagesTest +{ + @Test + public void should_define_preference_page_ids() + { + assertEquals("org.moreunit.core.preferences.featuredLanguagesPage", PreferencePages.FEATURED_LANGUAGES); + assertEquals("org.moreunit.core.preferences.otherLanguagesPage", PreferencePages.OTHER_LANGUAGES); + } +} diff --git a/org.moreunit.core.test/test/org/moreunit/core/resources/FolderCreationExceptionTest.java b/org.moreunit.core.test/test/org/moreunit/core/resources/FolderCreationExceptionTest.java new file mode 100644 index 00000000..749e9644 --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/resources/FolderCreationExceptionTest.java @@ -0,0 +1,34 @@ +package org.moreunit.core.resources; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.runtime.CoreException; +import org.junit.jupiter.api.Test; + +public class FolderCreationExceptionTest +{ + @Test + public void should_wrap_core_exception_and_expose_folder() + { + CoreException cause = mock(CoreException.class); + IFolder folder = mock(IFolder.class); + + FolderCreationException exception = new FolderCreationException(cause, folder); + + assertSame(folder, exception.getFolder()); + assertNotNull(exception.getCause()); + } + + @Test + public void should_create_exception_without_folder() + { + CoreException cause = mock(CoreException.class); + + FolderCreationException exception = new FolderCreationException(cause, null); + + assertNotNull(exception); + } +} diff --git a/org.moreunit.core.test/test/org/moreunit/core/ui/FileContentProviderTest.java b/org.moreunit.core.test/test/org/moreunit/core/ui/FileContentProviderTest.java new file mode 100644 index 00000000..dd47817b --- /dev/null +++ b/org.moreunit.core.test/test/org/moreunit/core/ui/FileContentProviderTest.java @@ -0,0 +1,59 @@ +package org.moreunit.core.ui; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; + +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.junit.jupiter.api.Test; + +public class FileContentProviderTest +{ + @Test + public void should_provide_files_as_elements() + { + IFile a = mock(IFile.class); + IFile b = mock(IFile.class); + + FileContentProvider provider = new FileContentProvider(List.of(a, b), null); + + Object[] elements = provider.getElements(null); + assertEquals(2, elements.length); + } + + @Test + public void should_use_preferred_file_as_default_selection() + { + IFile a = mock(IFile.class); + IFile preferred = mock(IFile.class); + + FileContentProvider provider = new FileContentProvider(List.of(a, preferred), preferred); + + assertNotNull(provider.getDefaultSelection()); + } + + @Test + public void should_have_no_children() + { + IFile file = mock(IFile.class); + + FileContentProvider provider = new FileContentProvider(List.of(file), null); + + assertNull(provider.getChildren(file)); + assertFalse(provider.hasChildren(file)); + } + + @Test + public void should_have_no_parent() + { + IFile file = mock(IFile.class); + + FileContentProvider provider = new FileContentProvider(List.of(file), null); + + assertNull(provider.getParent(file)); + } +} diff --git a/org.moreunit.mock.test/test/org/moreunit/mock/templates/NoDependenciesToMockExceptionTest.java b/org.moreunit.mock.test/test/org/moreunit/mock/templates/NoDependenciesToMockExceptionTest.java new file mode 100644 index 00000000..8f7943c1 --- /dev/null +++ b/org.moreunit.mock.test/test/org/moreunit/mock/templates/NoDependenciesToMockExceptionTest.java @@ -0,0 +1,26 @@ +package org.moreunit.mock.templates; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.eclipse.jdt.core.IType; +import org.junit.jupiter.api.Test; + +public class NoDependenciesToMockExceptionTest +{ + @Test + public void should_throw_exception_for_class_with_no_dependencies() + { + IType classUnderTest = mock(IType.class); + when(classUnderTest.getElementName()).thenReturn("MyClass"); + + NoDependenciesToMockException exception = new NoDependenciesToMockException(classUnderTest); + + assertNotNull(exception); + // message contains class name + assertNotNull(exception.getMessage()); + assertSame(MockingTemplateException.class, exception.getClass().getSuperclass()); + } +} diff --git a/org.moreunit.test/test/org/moreunit/actions/CreateTestMethodHierarchyActionTest.java b/org.moreunit.test/test/org/moreunit/actions/CreateTestMethodHierarchyActionTest.java new file mode 100644 index 00000000..bc88938b --- /dev/null +++ b/org.moreunit.test/test/org/moreunit/actions/CreateTestMethodHierarchyActionTest.java @@ -0,0 +1,22 @@ +package org.moreunit.actions; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.junit.jupiter.api.Test; + +public class CreateTestMethodHierarchyActionTest +{ + @Test + public void should_accept_selection_and_ignore_non_structured_selection() + { + CreateTestMethodHierarchyAction action = new CreateTestMethodHierarchyAction(); + + ISelection nonStructured = mock(ISelection.class); + action.selectionChanged(null, nonStructured); + // run with non-structured selection should do nothing (no exception) + action.run(mock(IAction.class)); + } +}