diff --git a/tests/unit_tests/README.md b/tests/unit_tests/README.md index e69de29b..7bfbb374 100644 --- a/tests/unit_tests/README.md +++ b/tests/unit_tests/README.md @@ -0,0 +1,60 @@ +# Unit Testing +## HERON +The `test_heron.py` file contains unit tests for the `FORCE/src/heron/create_componentsets_in_HERON()` function. These are designed to help developers identify bugs and isolate their causes. To this end, a brief overview of the most important unique scenarios each unit test addresses is listed below. + +### TestMinimalInput +This test is designed to check main functionality with minimal edge cases. It checks that: +- New Component node was added and updated with content, including: + - economics node + - capex CashFlow, with all attributes + - reference price + - reference driver, with conversion from kW to mW + +### TestExpandedInput1 +This test considers a more complex HERON input XML. It checks that: +- Case node is transferred uncorrupted +- Contents of multiple existing components that should not be updated are uncorrupted +- New component's reference driver value is not converted if input is in mW +- DataGenerators node is transferred uncorrupted + +### TestExpandedInput2 +This test focuses on ensuring correct updating of economics and CashFlow nodes and subnodes. It checks that: +- Multiple components that need updating can be merged correctly with new components +- Existing CashFlow of type non-capex is uncorrupted +- capex CashFlow is added when a non-capex CashFlow exists but a capex CashFlow does not +- economics node is found successfully when non-economics subnode of the component precedes the economics subnode +- CashFlows are updated correctly when both a non-capex and a capex CashFlow exists +- Non-CashFlow subnode of economics node is uncorrupted and CashFlow is still found +- Existing capex CashFlow has + - Uncorrupted attributes + - Uncorrupted subnode that does not need updating + - All three existing subnodes that need updating correctly replaced +- CashFlow subnodes are updated correctly when two of the three types that need updating exist + +### TestNoComponentsNode +This test examines a single edge case where the Components node is missing from the HERON input XML script. It checks that: +- A new Components node is created if one does not exist +- The new Component node is placed within this new Components node + +### TestNoComponentNodes +This test examines the edge case where a Components node exists, but no Component nodes. It checks that: +- The new component is added, with content + +### TestMissingSubnodes +This test considers scenarios where a component that needs to be updated is missing an economics or CashFlow subnode. It checks that: +- If the economics node is missing, it is created with correct content +- If the CashFlow node is missing, it is created with correct content + +### TestEmptyCompSetsFolder +This test checks the function's behavior when the provided component set folder is empty. It checks that: +- The open function was not called (e.g., no file was opened) +- The existing component was not corrupted + +### TestCompSetsFolderWithBadJSON +This test ensures a correct response to a component set file that is not is proper JSON format. It checks that: +- The function throws a ValueError + +### TestCompSetsFolderMultFiles +This test checks the function's filtering system regarding which component set files it should open. It checks that: +- Only files whose names start with "componentSet" are opened +- Only files of type .txt or .json are opened \ No newline at end of file diff --git a/tests/unit_tests/test_heron.py b/tests/unit_tests/test_heron.py index 75c15691..68320d57 100644 --- a/tests/unit_tests/test_heron.py +++ b/tests/unit_tests/test_heron.py @@ -1,61 +1,673 @@ +import os +import sys import unittest -from unittest.mock import mock_open, patch, MagicMock +from unittest.mock import mock_open, patch, call, MagicMock import xml.etree.ElementTree as ET +FORCE_LOC = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) +sys.path.append(FORCE_LOC) from FORCE.src.heron import create_componentsets_in_HERON -class TestCreateComponentSetsInHERON(unittest.TestCase): - - def setUp(self): - # Example of a minimal XML structure - self.heron_xml = """ - - - - - 100 - - - - - """ - self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) - - @patch('xml.etree.ElementTree.parse') - @patch('os.listdir') - @patch('builtins.open', - new_callable=mock_open, - read_data="""{ - "Component Set Name": "NewComponent", - "Reference Driver": 1000, - "Reference Driver Power Units": "kW", - "Reference Price (USD)": 2000, - "Scaling Factor": 0.5 - }""") - def test_new_component_creation(self, mock_file, mock_listdir, mock_parse): - # Setup the mock to return an XML tree - mock_parse.return_value = self.tree - mock_listdir.return_value = ['componentSet1.json'] - - # Call the function - result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") - - # Verify the XML was updated correctly - components = result_tree.findall('.//Component[@name="NewComponent"]') - self.assertEqual(len(components), 1) - economics = components[0].find('economics') - self.assertIsNotNone(economics) - - # Verify the CashFlow node was created - cashflows = economics.findall('CashFlow') +class HERONTestCase(unittest.TestCase): + """ + Allows for the addition of more complex checks than the `assert` options in the unittest.TestCase class + """ + + def check_reference_price(self, cashflow, correct_value, correct_content_length=1): + """ + Checks that the reference price of the given cashflow was correctly updated + @ In, cashflow, ET.Element, a xml node within which to check the reference price + @ In, correct_value, string, the expected value for the reference price's subnode + @ In, correct_content_length, int, optional, the expected total number of subnodes to + @ Out, None + """ + ref_price = cashflow.findall('./reference_price') + self.assertEqual(len(ref_price), 1) + ref_price_contents = [e for e in ref_price[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(ref_price_contents), correct_content_length) + ref_price_value = ref_price[0].findall('./fixed_value') + self.assertEqual(ref_price_value[0].text, correct_value) + + def check_reference_driver(self, cashflow, correct_value, correct_content_length=1): + """ + Checks that the reference driver of the given cashflow was correctly updated + @ In, cashflow, ET.Element, a xml node within which to check the reference driver + @ In, correct_value, string, the expected value for the reference driver's subnode + @ In, correct_content_length, int, optional, the expected total number of subnodes to + @ Out, None + """ + ref_driver = cashflow.findall('./reference_driver') + self.assertEqual(len(ref_driver), 1) + ref_driver_contents = [e for e in ref_driver[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(ref_driver_contents), correct_content_length) + ref_driver_value = ref_driver[0].findall('./fixed_value') + self.assertEqual(ref_driver_value[0].text, correct_value) + + def check_scaling_factor(self, cashflow, correct_value, correct_content_length=1): + """ + Checks that the scaling factor of the given cashflow was correctly updated + @ In, cashflow, ET.Element, a xml node within which to check the scaling factor + @ In, correct_value, string, the expected value for the scaling factor's subnode + @ In, correct_content_length, int, optional, the expected total number of subnodes to + @ Out, None + """ + scaling_factor = cashflow.findall('./scaling_factor_x') + self.assertEqual(len(scaling_factor), 1) + scaling_factor_contents = [e for e in scaling_factor[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(scaling_factor_contents), correct_content_length) + scaling_factor_value = scaling_factor[0].findall('./fixed_value') + self.assertEqual(scaling_factor_value[0].text, correct_value) + +class TestMinimalInput(HERONTestCase): + + def setUp(self): + # Example of a minimal XML structure + self.heron_xml = """ + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "kW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_minimal_input(self, mock_file, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = ['componentSetFake.json'] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify the XML was updated correctly + new_components = result_tree.findall('.//Component[@name="NewComponent"]') + self.assertEqual(len(new_components), 1) + economics = new_components[0].find('economics') + self.assertIsNotNone(economics) + + # Verify the CashFlow node was created with attribs + cashflows = economics.findall('CashFlow') + self.assertEqual(len(cashflows), 1) + self.assertEqual(cashflows[0].attrib['name'], 'NewComponent_capex') + self.assertIn('type', cashflows[0].keys()) + self.assertIn('taxable', cashflows[0].keys()) + self.assertIn('inflation', cashflows[0].keys()) + + # Verify reference price and reference driver + self.check_reference_driver(cashflows[0], '1.0') + self.check_reference_price(cashflows[0], '-2000') + +class TestExpandedInput1(HERONTestCase): + + def setUp(self): + # Added case and datagenerator nodes (should be transferred blindly) and extra components + self.heron_xml = """ + + + + + + + + + + + + + + + + + + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_expanded_input_1(self, mock_open, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + # No additional data for XML + mock_listdir.return_value = ["componentSetFake.json"] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify Case node was transferred + with self.subTest("Case node has been corrupted"): + cases = result_tree.findall('./Case') + self.assertEqual(len(cases), 1) + self.assertIsNotNone(cases[0].find('./untouched_content_Case')) + + # Verify Component nodes were merged + component_nodes = result_tree.findall("./Components/Component") + self.assertEqual(len(component_nodes), 3) + + for comp in component_nodes: + cashflows = comp.findall('./economics/CashFlow') + if comp.get('name') == 'ExistingComponent0': + # Verify CashFlow with type + self.assertEqual(len(cashflows), 1) + self.assertEqual(cashflows[0].attrib['type'], 'one-time') + elif comp.get('name') == 'ExistingComponent1': + # Verify CashFlow with type + self.assertEqual(len(cashflows), 1) + self.assertEqual(cashflows[0].attrib['type'], 'repeating') + elif comp.get('name') == 'NewComponent': + # Verify reference driver + self.check_reference_driver(cashflows[0], '1000') + + # Verify DataGenerators node was transferred + with self.subTest("DataGenerators node has been corrupted"): + data_gens = result_tree.findall('./DataGenerators') + self.assertEqual(len(data_gens), 1) + self.assertIsNotNone(data_gens[0].find('./untouched_content_DG')) + +class TestExpandedInput2(HERONTestCase): + + def setUp(self): + # Complex subnodes to each component with various and positions and configurations + self.heron_xml = """ + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + 1234 + + + more_rom_stuff + + + + + 8 + 0 + + + + + 1234 + + + + + + + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + def test_expanded_input_2(self, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + # No additional data for XML + mock_listdir.return_value = ["componentSetFake0.json", "componentSetFake1.json", "componentSetFake2.json", "componentSetFake3.json"] + + # Set up the open mock to return different files each time it's used + mock_open_mult = mock_open() + mock_open_mult.side_effect = [mock_open(read_data = + """{ + "Component Set Name": "Component0", + "Reference Driver": 1000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 1000, + "Scaling Factor": 0.1 + }""").return_value, + mock_open(read_data = + """{ + "Component Set Name": "Component1", + "Reference Driver": 2100, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 2200, + "Scaling Factor": 0.2 + }""").return_value, + mock_open(read_data = + """{ + "Component Set Name": "Component2", + "Reference Driver": 3100, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 3200, + "Scaling Factor": 0.3 + }""").return_value, + mock_open(read_data = + """{ + "Component Set Name": "Component3", + "Reference Driver": 4100, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 4200, + "Scaling Factor": 0.4 + }""").return_value] + + + # Call the function with patch for open function + with patch('builtins.open', mock_open_mult): + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify Component nodes were merged + component_nodes = result_tree.findall("./Components/Component") + self.assertEqual(len(component_nodes), 4) + + for comp in component_nodes: + economics = comp.findall('./economics') + self.assertEqual(len(economics), 1) + + if comp.get('name') == 'Component0': + # Verify non-capex cashflow was not corrupted + with self.subTest("Non-capex CashFlow node was corrupted"): + cashflow_non_capex = economics[0].findall('./CashFlow[@name="other"]') + self.assertEqual(len(cashflow_non_capex), 1) + cf_noncap_contents = [e for e in cashflow_non_capex[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(cf_noncap_contents), 1) + self.assertIsNotNone(cf_noncap_contents[0].tag, 'untouched_content') + + # Verify capex cashflow was added + with self.subTest("capex CashFlow was not added correctly"): + cashflow_capex = economics[0].findall('./CashFlow[@name="Component0_capex"]') + self.assertEqual(len(cashflow_capex), 1) + + elif comp.get('name') == 'Component1': + # Verify number of cashflows + self.assertEqual(len(economics[0].findall('./CashFlow')), 2) + + # Verify non-capex cashflow was not corrupted + with self.subTest("Non-capex CashFlow node was corrupted"): + cashflow_non_capex = economics[0].findall('./CashFlow[@name="other"]') + self.assertEqual(len(cashflow_non_capex), 1) + self.assertEqual(cashflow_non_capex[0].attrib['type'], 'one-time') + cashflow_noncap_contents = [e for e in cashflow_non_capex[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(cashflow_noncap_contents), 0) + + # Verify capex cashflow was updated + with self.subTest("capex CashFlow node was not updated correctly"): + cashflow_capex = economics[0].findall('./CashFlow[@name="capex"]') + self.assertEqual(len(cashflow_capex), 1) + self.check_reference_price(cashflow_capex[0], '-2200') + + elif comp.get('name') == 'Component2': + # Verify lifetime was not corrupted + with self.subTest("Non-cashflow child node of economics has been corrupted"): + proj_time = economics[0].findall('./lifetime') + self.assertEqual(len(proj_time), 1) + self.assertEqual(proj_time[0].text, '1') + + # Verify cashflow merging + cashflows = economics[0].findall('./CashFlow') self.assertEqual(len(cashflows), 1) - self.assertEqual(cashflows[0].attrib['name'], 'NewComponent_capex') - # Verify the reference driver and price updates - ref_driver = cashflows[0].find('./reference_driver/fixed_value') - self.assertEqual(ref_driver.text, '1.0') # The driver should have been converted from kW to MW + # Verify cashflow was updated correctly + + # Attributes + with self.subTest("Attributes of CashFlow were corrupted"): + self.assertIn('npv_exempt', cashflows[0].keys()) + + # Children + with self.subTest("CashFlow children nodes were not updated correctly"): + cf_contents = [e for e in cashflows[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(cf_contents), 4) + + self.check_reference_driver(cashflows[0], '3100') + self.check_reference_price(cashflows[0], '-3200') + self.check_scaling_factor(cashflows[0], '0.3') + + with self.subTest("Existing CashFlow child node was corrupted"): + # Driver node + driver = cashflows[0].findall('./driver') + self.assertEqual(len(driver), 1) + self.assertIsNotNone(driver[0].findall('fixed_value')) + + elif comp.get('name') == 'Component3': + cashflows = comp.findall('./economics/CashFlow') + self.assertEqual(len(cashflows), 1) + + # Verify cashflow was updated correctly + with self.subTest("CashFlow children nodes were not updated correctly"): + cf_contents = [e for e in cashflows[0].findall('./') + if not e.tag is ET.Comment] # Filters out ET.Comment elements + self.assertEqual(len(cf_contents), 3) + + self.check_reference_driver(cashflows[0], '4100') + self.check_reference_price(cashflows[0], '-4200') + self.check_scaling_factor(cashflows[0], '0.4') + +class TestNoComponentsNode(HERONTestCase): + + def setUp(self): + # Has no Components node + self.heron_xml = """ + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_no_comps_node(self, mock_file, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = ['componentSetFake.json'] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify components node was created with contents + components = result_tree.findall('./Components') + self.assertEqual(len(components), 1) + self.assertEqual(len(components[0].findall('./Component[@name="NewComponent"]')), 1) + +class TestNoComponentNodes(HERONTestCase): + + def setUp(self): + # Has no Component nodes + self.heron_xml = """ + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "kW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_no_comp_nodes(self, mock_file, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = ['componentSetFake.json'] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify component node was created + component_list = result_tree.findall('./Components/Component') + self.assertEqual(len(component_list), 1) + self.assertEqual(component_list[0].attrib['name'], 'NewComponent') + + # Verify contents have been added + self.assertIsNotNone(component_list[0].findall('./economics/CashFlow')) + +class TestMissingSubnodes(HERONTestCase): + + def setUp(self): + # Comp0 has no economics subnode; Comp1 has no CashFlow subnode + self.heron_xml = """ + + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + def test_missing_subnodes(self, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = ['componentSetFake0.json', 'componentSetFake1.json'] + + # Set up the open mock to return different files each time it's used + mock_open_mult = mock_open() + mock_open_mult.side_effect = [mock_open(read_data = + """{ + "Component Set Name": "Component0", + "Reference Driver": 1000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 1000, + "Scaling Factor": 0.1 + }""").return_value, + mock_open(read_data = + """{ + "Component Set Name": "Component1", + "Reference Driver": 2000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.2 + }""").return_value] + + # Call the function with patch for open function + with patch('builtins.open', mock_open_mult): + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify component nodes + comp0 = result_tree.findall('./Components/Component[@name="Component0"]') + self.assertEqual(len(comp0), 1) + comp1 = result_tree.findall('./Components/Component[@name="Component1"]') + self.assertEqual(len(comp1), 1) + + # Verify comp0 updated correctly + with self.subTest("economics node and subnodes were not added correctly"): + economics = comp0[0].findall('./economics') + self.assertEqual(len(economics), 1) + cashflows = economics[0].findall('./CashFlow') + self.assertEqual(len(cashflows), 1) + self.check_reference_driver(cashflows[0], '1000') + + # Verify comp1 updated correctly + with self.subTest("cashflow node and subnodes were not added correctly"): + cashflows = comp1[0].findall('./economics/CashFlow') + self.assertEqual(len(cashflows), 1) + self.check_reference_driver(cashflows[0], '2000') + +class TestEmptyCompSetsFolder(HERONTestCase): + def setUp(self): + # Example of a minimal XML structure + self.heron_xml = """ + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + # This mock_open should not be called in the function + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "kW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_empty_compsets_folder(self, mock_open, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = [] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + + # Verify open function was not called + mock_open.assert_not_called() + + components_nodes = result_tree.findall('./Components') + + # Verify component node was not corrupted + component_nodes = components_nodes[0].findall('./Component') + self.assertEqual(len(component_nodes), 1) + self.assertEqual(component_nodes[0].attrib['name'], 'ExistingComponent') + +class TestCompSetsFolderWithBadJSON(HERONTestCase): + def setUp(self): + # Example of a minimal XML structure + self.heron_xml = """ + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + @patch('builtins.open', + new_callable=mock_open, + read_data="Example of bad format") + def test_compsets_folder_bad_json(self, mock_open, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + mock_listdir.return_value = ['componentSetFake.json'] + + # Call the function and check for error + caught_bad_json = False + try: + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") + except ValueError: + caught_bad_json = True + + with self.subTest("Did not respond correctly to bad component set file content"): + self.assertEqual(caught_bad_json, True) + +class TestCompSetsFolderMultFiles(HERONTestCase): + def setUp(self): + # Example of a minimal XML structure + self.heron_xml = """ + + + + + + + + + """ + self.tree = ET.ElementTree(ET.fromstring(self.heron_xml)) + + @patch('xml.etree.ElementTree.parse') + @patch('os.listdir') + # Open mock will return the same read_data each time it is called + # This is acceptable only because the content of the result tree is untested + @patch('builtins.open', + new_callable=mock_open, + read_data="""{ + "Component Set Name": "NewComponent", + "Reference Driver": 1000, + "Reference Driver Power Units": "mW", + "Reference Price (USD)": 2000, + "Scaling Factor": 0.5 + }""") + def test_compsets_folder_mult_files(self, mock_open, mock_listdir, mock_parse): + # Set up the parse mock to return an XML tree + mock_parse.return_value = self.tree + files_list = ['component.json', 'README', 'componentSet.csv', 'xcomponentSet.json', + 'componentSet.json', 'componentSetStuff.txt', + 'aFolder', 'Set.json', 'compSet.json', 'ComponentSet.json'] + mock_listdir.return_value = files_list + # Only the txt and json files whose names start with 'componentSet' should be opened + acceptable_files = ['componentSet.json', 'componentSetStuff.txt'] + + # Call the function + result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml") - # Add more tests here to cover other conditions and edge cases + # Verify open function was called on correct files + for file in files_list: + # if file should have been opened + if file in acceptable_files: + with self.subTest(msg="File was not opened and should have been", file = file): + # Verify file was opened + self.assertIn(call('/fake/folder/'+file), mock_open.call_args_list) + # if file should not have been opened + else: + with self.subTest(msg="File was opened and should not have been", file = file): + # Verify file was not opened + self.assertNotIn(call('/fake/folder/'+file), mock_open.call_args_list) +# This is not needed for running tests through FORCE/run_tests +# It does allow tests to be run via the unit tester when test_heron is run directly if __name__ == '__main__': - unittest.main() + unittest.main() diff --git a/tests/unit_tests/tests b/tests/unit_tests/tests new file mode 100644 index 00000000..4a340f4e --- /dev/null +++ b/tests/unit_tests/tests @@ -0,0 +1,46 @@ +[Tests] + [./TestMinimalInput] + type = RavenPython + input = '-m unittest test_heron.TestMinimalInput' + [../] + + [./TestExpandedInput1] + type = RavenPython + input = '-m unittest test_heron.TestExpandedInput1' + [../] + + [./TestExpandedInput2] + type = RavenPython + input = '-m unittest test_heron.TestExpandedInput2' + [../] + + [./TestNoComponentsNode] + type = RavenPython + input = '-m unittest test_heron.TestNoComponentsNode' + [../] + + [./TestNoComponentNodes] + type = RavenPython + input = '-m unittest test_heron.TestNoComponentNodes' + [../] + + [./TestMissingSubnodes] + type = RavenPython + input = '-m unittest test_heron.TestMissingSubnodes' + [../] + + [./TestEmptyCompSetsFolder] + type = RavenPython + input = '-m unittest test_heron.TestEmptyCompSetsFolder' + [../] + + [./TestCompSetsFolderWithBadJSON] + type = RavenPython + input = '-m unittest test_heron.TestCompSetsFolderWithBadJSON' + [../] + + [./TestCompSetsFolderMultFiles] + type = RavenPython + input = '-m unittest test_heron.TestCompSetsFolderMultFiles' + [../] +[]