{"id":5869,"date":"2023-07-17T07:00:26","date_gmt":"2023-07-17T14:00:26","guid":{"rendered":"http:\/\/writeasync.net\/?p=5869"},"modified":"2023-07-15T13:57:12","modified_gmt":"2023-07-15T20:57:12","slug":"stay-com-implementation-classes","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5869","title":{"rendered":"Stay COM: stubs and testing"},"content":{"rendered":"<p>Previously, we built a <a href=\"http:\/\/writeasync.net\/?p=5867\">Windows Task Scheduler sample application<\/a> using the COM API via WIL. As far as the client code was concerned, COM was a detail encapsulated by the C++ facade we created:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nvoid run()\r\n{\r\n    auto cleanup = init_com();\r\n    auto service = TaskService::connect();\r\n    auto folder = service.get_root_folder();\r\n    auto task = service.create_task();\r\n    task.set_author(L&quot;Author Name&quot;);\r\n    task.set_logon_type(TASK_LOGON_INTERACTIVE_TOKEN);\r\n    task.set_settings(true, std::chrono::minutes(5));\r\n    task.add_time_trigger(L&quot;Trigger1&quot;, make_date_time(2005y \/ 1 \/ 1, 12h + 5min), make_date_time(2015y \/ 5 \/ 2, 8h));\r\n    task.add_exec_action(get_executable_path());\r\n    folder.save(task, L&quot;Time Trigger Test Task&quot;);\r\n}\r\n<\/pre>\n<p>Not a COM pointer to be seen here! However, the implementation code has all the gory details:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    void Task::set_author(LPCWSTR author)\r\n    {\r\n        wil::com_ptr&lt;IRegistrationInfo&gt; pRegInfo;\r\n        THROW_IF_FAILED_MSG(m_task-&gt;get_RegistrationInfo(pRegInfo.put()), &quot;Cannot get identification pointer&quot;);\r\n        auto value = wil::make_bstr(author);\r\n        THROW_IF_FAILED_MSG(pRegInfo-&gt;put_Author(value.get()), &quot;Cannot put identification info&quot;);\r\n    }\r\n<\/pre>\n<p>These details are unavoidable &#8212; they are <a href=\"https:\/\/simplicable.com\/new\/accidental-complexity-vs-essential-complexity\">essential complexity<\/a>. But the problem is that nothing here is testable and therefore, nothing is tested. The entire application thus far is contained in <code>main.cpp<\/code>! To have a chance of testability, we need to break out the components into separate <a href=\"https:\/\/stackoverflow.com\/questions\/1106149\/what-is-a-translation-unit-in-c\">translation units<\/a> and validate them in our unit test project. So, let&#8217;s begin.<\/p>\n<p>The easiest candidate for extraction is the date\/time code. We can simply move it into our static library, splitting declarations into a header and definitions (well, the singular definition) into an implementation file. Now, it is testable:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nTEST(date_time_test, make)\r\n{\r\n    auto dt = make_date_time(2001y \/ 2 \/ 3, 4h + 5min + 6s);\r\n\r\n    ASSERT_EQ(&quot;2001-02-03 04:05:06&quot;, std::format(&quot;{:%Y-%m-%d %T}&quot;, dt));\r\n}\r\n<\/pre>\n<p>This change was easy: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/0938398da65d7d3230157614ea82b55a35007093\">Move DateTime to lib, add test<\/a><\/p>\n<p>However, that change has nothing to do with COM. It&#8217;s more of an exercise in setting up our test\/development cycle, and so far <a href=\"https:\/\/stackoverflow.com\/questions\/276813\/what-is-red-green-testing\">we&#8217;re green<\/a>. The next step is to extract one of the COM facade classes (easy enough: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/53c37aaec33531e5418a5999332a4790e20ff34a\">Move Task to lib<\/a>) and then comprehensively test it (!!!).<\/p>\n<p>This is the type of test we would like to write, eventually:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nTEST(task_test, basic_scenario)\r\n{\r\n    Task task(make_stub_task_definition());\r\n\r\n    task.set_author(L&quot;A. Person&quot;);\r\n    task.set_logon_type(TASK_LOGON_SERVICE_ACCOUNT);\r\n    task.set_settings(false, 59min);\r\n    task.add_time_trigger(\r\n        L&quot;Trigger1&quot;,\r\n        make_date_time(2020y \/ 10 \/ 20, 15h + 37min + 42s),\r\n        make_date_time(2022y \/ 11 \/ 30, 7h + 6min + 59s));\r\n    task.add_exec_action(L&quot;C:\\\\WINDOWS\\\\System32\\\\notepad.exe&quot;);\r\n\r\n    auto expected = L&quot;. . . something . . .&quot;;\r\n    assert_state(task, expected);\r\n}\r\n<\/pre>\n<p>But there are clearly several prerequisites to get there from here. Before we dive deep on the end-to-end, let&#8217;s start from a more modest position. Maybe we can test just the first thing, <code>set_author<\/code>. Since we&#8217;re practicing <a href=\"https:\/\/www.codeproject.com\/articles\/151136\/test-last-development-a-primer-for-unit-testing-of\">legacy-style test last development<\/a> here, we can inspect the implementation code to see what we might need. Following the control flow, we would need at least these elements:<\/p>\n<ul>\n<li>An ITaskDefinition interface implementation, to use with the <code>Task<\/code> facade<\/li>\n<li>An ITaskDefinition::get_RegistrationInfo method, which returns an implementation of IRegistrationInfo<\/li>\n<li>An IRegistrationInfo::put_Author method<\/li>\n<\/ul>\n<p>Of course, we cannot use just any implementation of ITaskDefinition. We need a <a href=\"https:\/\/martinfowler.com\/articles\/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs\">stub class<\/a> of our own making so that we can control its behavior independently from the real Windows-owned implementation. This is important because we have to program error behavior for all the method calls above, such that those <code>THROW_IF_FAILED_MSG<\/code> paths are actually exercised.<\/p>\n<p>As usual for legacy code, the initial framework is not going to be easy to establish. We will need dozens, if not hundreds, of lines of code to build even the first stub. The consolation prize is that we can use a tiny bit of <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/uwp\/cpp-and-winrt-apis\/intro-to-using-cpp-with-winrt\">C++\/WinRT<\/a> to avoid the nightmare of raw COM implementation semantics. Hello, <a href=\"https:\/\/learn.microsoft.com\/en-us\/uwp\/cpp-ref-for-winrt\/implements\">winrt::implements<\/a>! First, we declare the class in a header file <code>stub_taskdef.h<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#pragma once\r\n\r\n#include &lt;windows.h&gt;\r\n#include &lt;taskschd.h&gt;\r\n\r\n#include &lt;wil\/cppwinrt.h&gt;\r\n\r\nnamespace wacpp::test\r\n{\r\n\r\nclass Stub_ITaskDefinition : public winrt::implements&lt;Stub_ITaskDefinition, ITaskDefinition&gt;\r\n{\r\npublic:\r\n    \/\/ ITaskDefinition\r\n\r\n    STDMETHODIMP get_RegistrationInfo(\r\n        IRegistrationInfo** ppRegistrationInfo) noexcept override;\r\n\r\n    STDMETHODIMP put_RegistrationInfo(\r\n        IRegistrationInfo* pRegistrationInfo) noexcept override;\r\n\r\n    \/\/ . . . more methods . . .\r\n\r\n    \/\/ IDispatch\r\n\r\n    STDMETHODIMP GetTypeInfoCount(\r\n        UINT* pctinfo) noexcept override;\r\n\r\n    \/\/ . . . more methods . . .\r\n};\r\n\r\n}\r\n<\/pre>\n<p>Then we define the class methods in <code>stub_taskdef.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;stub_taskdef.h&quot;\r\n\r\nnamespace wacpp::test\r\n{\r\n\r\nSTDMETHODIMP Stub_ITaskDefinition::get_RegistrationInfo(\r\n    &#x5B;&#x5B;maybe_unused]] IRegistrationInfo** ppRegistrationInfo) noexcept\r\n{\r\n    return E_NOTIMPL;\r\n}\r\n\r\n\/\/ . . . more methods . . . \r\n\r\n}\r\n<\/pre>\n<p>By default, we won&#8217;t observe any of the method parameters and always return <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/seccrypto\/common-hresult-values\">E_NOTIMPL<\/a> &#8212; truly, the stubbiest of stubs. Note that ITaskDefinition uses <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/com\/iunknown-and-interface-inheritance\">interface inheritance<\/a> with IDispatch, so we need both sets of methods on this stub. We also need the <code>winrt::implements<\/code> code, so we pull in those WinRT definitions via <a href=\"https:\/\/github.com\/microsoft\/wil\/wiki\/WIL-and-C---WinRT-together\">WIL&#8217;s cppwinrt.h header<\/a>. This is not strictly necessary, but it&#8217;s simpler given that we already have WIL and enables one very important scenario for us: <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/uwp\/cpp-and-winrt-apis\/author-coclasses#enabling-classic-com-support\">classic COM support<\/a>. That&#8217;s right, we do not have to implement IUnknown or any of that boilerplate, and the resulting component should look just like a &#8220;real&#8221; COM type to any consumer. First stub done: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/66e1a484f17a80c222127e0ea9a0be9b5ef3ada1\">Create stub for ITaskDefinition<\/a><\/p>\n<p>With stub number one out of the way, we move on to number two, IRegistrationInfo. The details are very much the same as before, so we make short work of it: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/f5b55a2347f98b2f546d994b6f527c6ca073d02e\">Create stub for IRegistrationInfo<\/a><\/p>\n<p>Now we need to start writing the first test. If we want to test every path of <code>set_author<\/code>, we can follow this recipe:<\/p>\n<ol>\n<li>Initialize the stub such that all inner calls will fail.<\/li>\n<li>Call the method under test and observe the failure.<\/li>\n<li>Set up the stub to make the currently failing inner call succeed instead.<\/li>\n<li>Repeat the above until every call succeeds.<\/li>\n<li>Assert the final successful status.<\/li>\n<\/ol>\n<p>It would make sense initially to tackle initialization. But what to initialize? Well, we need a class which overrides the default stub behavior with our eventual test-specific logic, thus:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;gtest\/gtest.h&gt;\r\n\r\n#include &lt;windows.h&gt;\r\n\r\n#include &lt;wil\/com.h&gt;\r\n\r\n#include &quot;stub_taskdef.h&quot;\r\n#include &quot;task.h&quot;\r\n\r\nnamespace\r\n{\r\n    class StubTaskDefinition : public wacpp::test::Stub_ITaskDefinition\r\n    {\r\n    };\r\n\r\n    wil::com_ptr&lt;ITaskDefinition&gt; make_stub_task_definition()\r\n    {\r\n        auto ptr = winrt::make&lt;StubTaskDefinition&gt;();\r\n        return wil::make_com_ptr(ptr.detach());\r\n    }\r\n}\r\n\r\nnamespace wacpp::test\r\n{\r\n\r\n    TEST(task_test, set_author)\r\n    {\r\n        Task task(make_stub_task_definition());\r\n\r\n        ASSERT_THROW(task.set_author(L&quot;Fail 1&quot;), wil::ResultException);\r\n    }\r\n\r\n}\r\n<\/pre>\n<blockquote><p>Depending on which Windows SDK version you are using, you might already encounter build errors at this step. Most likely these errors originate from the WinRT base.h header which contains <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/uwp\/cpp-and-winrt-apis\/consume-apis#cwinrt-projection-headers\">basic support code for all WinRT projection types<\/a> and is included by WIL. There are many possible ways to address this, but one straightforward solution is to install the Windows 11 SDK and update your CMakeLists.txt to use it (via <code>set(CMAKE_SYSTEM_VERSION 11.0)<\/code>).<\/p><\/blockquote>\n<p>There are a few things to note at this step, as seemingly trivial as it is. First, we cannot <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/uwp\/cpp-and-winrt-apis\/diag-direct-alloc\">directly instantiate winrt::implements types<\/a>. Instead, we use <code>winrt::make<\/code> which returns a <code>winrt::com_ptr<\/code>. Second, our implementation code expects <code>wil::com_ptr<\/code> so we have to do a small pointer swap here to get the final result. Since our test expects failure and the default stub always returns failure (via E_NOTIMPL), our test indeed throws an exception (specifically, a <a href=\"https:\/\/github.com\/microsoft\/wil\/wiki\/Error-handling-helpers#exception-guards\">wil::ResultException<\/a>) as we have asserted.<\/p>\n<p>But there is something unsatisfying about this. Isn&#8217;t it possible that, even though we threw an exception, the internal state of the object under test was modified in an incorrect way? Indeed, we need another assertion to show that the object is still consistent &#8212; whatever that means. And how would we even look at the state without violating encapsulation? One potential answer lies directly on the ITaskDefinition interface: <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/taskschd\/nf-taskschd-itaskdefinition-get_xmltext\">the get_XmlText method<\/a>. This is the supported way to export the task definition which we can exploit for observability. Let&#8217;s start with a canned implementation which we will extend over the course of our test&#8217;s evolution:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    STDMETHODIMP get_XmlText(BSTR* pXml) noexcept override\r\n    try\r\n    {\r\n        std::wstring xml{};\r\n        xml += L&quot;&lt;Task&gt;&quot;;\r\n        xml += L&quot;&lt;\/Task&gt;&quot;;\r\n\r\n        auto outer_xml = wil::make_bstr(xml.c_str());\r\n        *pXml = outer_xml.release();\r\n\r\n        return S_OK;\r\n    }\r\n    CATCH_RETURN()\r\n<\/pre>\n<p>Internally we deal in the land of standard C++, so <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/standard-library\/string-typedefs?view=msvc-170#wstring\">wstring<\/a> is our type. But for COM interop, we need to speak in <a href=\"https:\/\/learn.microsoft.com\/en-us\/previous-versions\/windows\/desktop\/automat\/bstr\">BSTR<\/a>s. WIL has simple conversion functions, though, so we&#8217;re well covered here. For now, we define our task XML format with one top-level element <code>Task<\/code>. It&#8217;s empty, indicating we have no state, which is true for the time being. Since all the STL container types (including <code>basic_string<\/code>) have the potential to throw exceptions, we have to use an <a href=\"https:\/\/github.com\/microsoft\/wil\/wiki\/Error-handling-helpers#guard-macros-and-helpers\">exception guard<\/a> to meet the <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/cpp\/noexcept-cpp?view=msvc-170\"><code>noexcept<\/code> specification<\/a> that all COM methods should follow. (Remember, COM uses error codes exclusively instead of <a href=\"https:\/\/stackoverflow.com\/questions\/2619630\/c-exceptions-binary-compatibility\">exceptions, for ABI safety<\/a>.)<\/p>\n<p>Now we can write an assert helper:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nvoid assert_xml(wacpp::Task&amp; task, const std::wstring&amp; expected)\r\n{\r\n    wil::unique_bstr str{};\r\n    THROW_IF_FAILED(task.get().get_XmlText(str.put()));\r\n    std::wstring actual = str.get();\r\n\r\n    ASSERT_EQ(expected, actual);\r\n}\r\n<\/pre>\n<p>Finally, we can assert the task XML in the test:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nTEST(task_test, set_author)\r\n{\r\n    Task task(make_stub_task_definition());\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 1&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n}\r\n<\/pre>\n<p>This is our skeleton so far: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/0a81c99243281eb3a94d5c3c689520f655039cbd\">Add skeleton for set_author test<\/a><\/p>\n<p>All this work, and yet we&#8217;ve only done the first part of our testing recipe. We now have to instruct our stub to not fail at the first step. Rather than inspecting the code this time, let&#8217;s change the assertion to help us discover the failure, say, by expecting <code>std::logic_error<\/code> instead of the correct exception type. Now if we run the test, we get the full error context:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n    Expected: task.set_author(L&quot;Fail 1&quot;) throws an exception of type std::logic_error.\r\n  Actual: it throws class wil::ResultException with description &quot;...\\src\\tasksched\\lib\\task.cpp(18)\\tasksched-test.exe!00007FF6133F15B8: (caller: 00007FF6133E52EA) Exception(1) tid(1b734) 80004001 Not implemented\r\n\r\n    Msg:&#x5B;Cannot get identification pointer] &#x5B;wacpp::Task::set_author(m_task-&gt;get_RegistrationInfo(pRegInfo.put()))]\r\n&quot;.\r\n<\/pre>\n<p>Aha! The inner failure is at get_RegistrationInfo, so we have to make <em>that<\/em> call succeed but fail the rest. Immediately, we have another problem; how exactly are we supposed to fail this call the first time, but not the second time? We need some state that we can manipulate so that the stub knows its immediate instructions. In standard C++ classes, the simplest way to do this involves <a href=\"https:\/\/en.wikipedia.org\/wiki\/Mutator_method#C++\">mutator methods<\/a> (AKA &#8220;setters&#8221;), but all we have is a COM pointer. One rather heavy-handed option is to implement our own COM interface with these setter methods, but that sounds like quite a chore. Why not pass the state from outside so that we retain control of it? This is a neat trick which is downright dangerous to use for your external API, but works well for these kinds of test stubs. Here is one potential implementation:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct Stub\r\n{\r\n    struct Data\r\n    {};\r\n\r\n    class TaskDefinition : public wacpp::test::Stub_ITaskDefinition\r\n    {\r\n    public:\r\n        TaskDefinition(const Data&amp; data)\r\n            : m_data(data)\r\n        {}\r\n\r\n        \/\/ . . .\r\n\r\n    private:\r\n        const Data&amp; m_data;\r\n    };\r\n};\r\n\r\nwil::com_ptr&lt;ITaskDefinition&gt; make_stub_task_definition(const Stub::Data&amp; data)\r\n{\r\n    auto ptr = winrt::make&lt;Stub::TaskDefinition&gt;(data);\r\n    return wil::make_com_ptr(ptr.detach());\r\n}\r\n\r\n\/\/ . . . \r\n\r\nTEST(task_test, set_author)\r\n{\r\n    Stub::Data data{};\r\n    Task task(make_stub_task_definition(data));\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 1&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n}\r\n<\/pre>\n<p>We have moved all the stub code into a wrapper struct (mostly for organization and namespacing benefits), created a new <code>Data<\/code> struct, and passed the data along from the test. It doesn&#8217;t do anything yet, so let&#8217;s create our first failure instruction:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct Data\r\n{\r\n    HRESULT get_RegistrationInfo_result{};\r\n};\r\n\r\n\/\/ . . .\r\n\r\nTEST(task_test, set_author)\r\n{\r\n    Stub::Data data{ .get_RegistrationInfo_result = E_FAIL };\r\n    Task task(make_stub_task_definition(data));\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 1&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n\r\n    data.get_RegistrationInfo_result = S_OK;\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 2&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n}\r\n<\/pre>\n<p>We start by telling the stub to use E_FAIL for the result of get_RegistrationInfo, then flip that status to S_OK and try again. Of course, our stub has to obey the instruction, which on success requires us to give out an IRegistrationInfo instance. So let&#8217;s carve out that stub:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;stub_reginfo.h&quot;\r\n\/\/ . . .\r\nnamespace\r\n{\r\n\r\nstruct Stub\r\n{\r\n    \/\/ . . .\r\n    class RegistrationInfo : public wacpp::test::Stub_IRegistrationInfo\r\n    {\r\n    public:\r\n        RegistrationInfo(const Data&amp; data)\r\n            : m_data(data)\r\n        {}\r\n\r\n    private:\r\n        const Data&amp; m_data;\r\n    };\r\n    \/\/ . . .\r\n}\r\n<\/pre>\n<p>Now we implement the method in question:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    class TaskDefinition : public wacpp::test::Stub_ITaskDefinition\r\n    {\r\n        \/\/ . . .\r\n        STDMETHODIMP get_RegistrationInfo(\r\n            IRegistrationInfo** ppRegistrationInfo) noexcept override\r\n        try\r\n        {\r\n            const auto hr = m_data.get_RegistrationInfo_result;\r\n            if (SUCCEEDED(hr))\r\n            {\r\n                auto ptr = winrt::make&lt;Stub::RegistrationInfo&gt;(m_data);\r\n                ptr.copy_to(ppRegistrationInfo);\r\n            }\r\n            \r\n            return hr;\r\n        }\r\n        CATCH_RETURN()\r\n        \/\/ . . .\r\n    };\r\n<\/pre>\n<p>The basic pattern is that we check our failure result, do the work if successful, and then faithfully return the result. (Note that WinRT\/C++ has most of the same goodies for its <code>com_ptr<\/code> as does WIL&#8217;s so it&#8217;s quite simple to return an out pointer!) The next phase of the test is done: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/7bed06a6fb93dbab469b5a25c869c14ddff4a5e6\">set_author test allows get_RegistrationInfo<\/a><\/p>\n<p>What is the next failure? Using our assert trick, we find it here:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nMsg:&#x5B;Cannot put identification info] &#x5B;wacpp::Task::set_author(pRegInfo-&gt;put_Author(value.get()))]\r\n<\/pre>\n<p>We address this by implementing the same flow as before but for put_Author:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct Stub\r\n{\r\n    struct Data\r\n    {\r\n        HRESULT get_RegistrationInfo_result{};\r\n        HRESULT put_Author_result{};\r\n    };\r\n\r\n    class RegistrationInfo : public wacpp::test::Stub_IRegistrationInfo\r\n    {\r\n    public:\r\n        RegistrationInfo(const Data&amp; data)\r\n            : m_data(data)\r\n            , m_author()\r\n        {}\r\n\r\n        STDMETHODIMP put_Author(\r\n            BSTR author) noexcept override\r\n        try\r\n        {\r\n            const auto hr = m_data.put_Author_result;\r\n            if (SUCCEEDED(hr))\r\n            {\r\n                m_author = author;\r\n            }\r\n\r\n            return hr;\r\n        }\r\n        CATCH_RETURN()\r\n\r\n    private:\r\n        const Data&amp; m_data;\r\n        std::wstring m_author;\r\n    };\r\n\r\n    \/\/ . . .\r\n}\r\n\/\/ . . .\r\nTEST(task_test, set_author)\r\n{\r\n    Stub::Data data{\r\n        .get_RegistrationInfo_result = E_FAIL,\r\n        .put_Author_result = E_FAIL,\r\n    };\r\n    Task task(make_stub_task_definition(data));\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 1&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n\r\n    data.get_RegistrationInfo_result = S_OK;\r\n\r\n    ASSERT_THROW(task.set_author(L&quot;Fail 2&quot;), wil::ResultException);\r\n\r\n    assert_xml(task, L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;);\r\n\r\n    data.put_Author_result = S_OK;\r\n\r\n    task.set_author(L&quot;Some Author&quot;);\r\n\r\n    const auto expected =\r\n        L&quot;&lt;Task&gt;&quot;\r\n        L&quot;&lt;Author&gt;Some Author&lt;\/Author&gt;&quot;\r\n        L&quot;&lt;\/Task&gt;&quot;;\r\n    assert_xml(task, expected);\r\n}\r\n<\/pre>\n<p>We now have the full test implemented, but it won&#8217;t pass:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n    Expected equality of these values:\r\n  expected\r\n    Which is: L&quot;&lt;Task&gt;&lt;Author&gt;Some Author&lt;\/Author&gt;&lt;\/Task&gt;&quot;\r\n  actual\r\n    Which is: L&quot;&lt;Task&gt;&lt;\/Task&gt;&quot;\r\n<\/pre>\n<p>Of course, we have not implemented the corresponding get_XmlText for our RegistrationInfo stub. Something like this should work:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    class RegistrationInfo \/\/ . . .\r\n    {\r\n        \/\/ . . .\r\n        STDMETHODIMP get_XmlText(\r\n            BSTR* pXml) noexcept override\r\n        try\r\n        {\r\n            std::wstring xml{};\r\n            if (!m_author.empty())\r\n            {\r\n                xml += std::format(L&quot;&lt;Author&gt;{}&lt;\/Author&gt;&quot;, m_author);\r\n            }\r\n\r\n            auto outer_xml = wil::make_bstr(xml.c_str());\r\n            *pXml = outer_xml.release();\r\n\r\n            return S_OK;\r\n        }\r\n        CATCH_RETURN()\r\n        \/\/ . . .\r\n    };\r\n<\/pre>\n<p>But now we have to remember the registration info object we handed out from our task definition stub, and call upon that object to compose our own XML result. Assuming we do not care if we overwrite the registration object every time, this implementation will work:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n    class TaskDefinition : public wacpp::test::Stub_ITaskDefinition\r\n    {\r\n    public:\r\n        TaskDefinition(const Data&amp; data)\r\n            : m_data(data)\r\n            , m_registration_info()\r\n        {}\r\n\r\n        STDMETHODIMP get_XmlText(\r\n            BSTR* pXml) noexcept override\r\n        try\r\n        {\r\n            std::wstring xml{};\r\n            xml += L&quot;&lt;Task&gt;&quot;;\r\n\r\n            if (m_registration_info)\r\n            {\r\n                wil::unique_bstr str;\r\n                THROW_IF_FAILED(m_registration_info-&gt;get_XmlText(str.put()));\r\n                xml += str.get();\r\n            }\r\n\r\n            xml += L&quot;&lt;\/Task&gt;&quot;;\r\n\r\n            auto outer_xml = wil::make_bstr(xml.c_str());\r\n            *pXml = outer_xml.release();\r\n\r\n            return S_OK;\r\n        }\r\n        CATCH_RETURN()\r\n\r\n        STDMETHODIMP get_RegistrationInfo(\r\n            IRegistrationInfo** ppRegistrationInfo) noexcept override\r\n        try\r\n        {\r\n            const auto hr = m_data.get_RegistrationInfo_result;\r\n            if (SUCCEEDED(hr))\r\n            {\r\n                m_registration_info = winrt::make&lt;Stub::RegistrationInfo&gt;(m_data);\r\n                m_registration_info.copy_to(ppRegistrationInfo);\r\n            }\r\n\r\n            return hr;\r\n        }\r\n        CATCH_RETURN()\r\n\r\n    private:\r\n        const Data&amp; m_data;\r\n        winrt::com_ptr&lt;IRegistrationInfo&gt; m_registration_info;\r\n    };\r\n<\/pre>\n<p>With all these changes, our test is green again: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/commit\/e9df5bd8e6f390000ee9f450287c487ca874a6fd\">Complete set_author test<\/a><\/p>\n<p>If we mechanically follow this procedure, oh, 10 or 15 more times across the set of COM interfaces and methods we use, eventually we&#8217;ll have full coverage of our Task class. It&#8217;s not glamorous work, but it sure beats untested code!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Previously, we built a Windows Task Scheduler sample application using the COM API via WIL. As far as the client code was concerned, COM was a detail encapsulated by the C++ facade we created: void run() { auto cleanup =&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[91,101,51],"tags":[],"class_list":["post-5869","post","type-post","status-publish","format-standard","hentry","category-design","category-native","category-testing"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5869","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5869"}],"version-history":[{"count":5,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5869\/revisions"}],"predecessor-version":[{"id":5874,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5869\/revisions\/5874"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5869"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5869"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5869"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}