title: How to expect (jest) a function to be called with an object that needs processing before it can be matched
body: <p>I'm testing that a (constructor) function is called with a particular object, however the object itself has properties which themselves are JSON. This is because the third-party API which I am mocking expects this type of object, so I can't change it.</p>
  <p>I want to test these JSON properties by converting them back to objects and using something like <code>expect.objectContaining</code>, because it is less brittle than trying to match the complete object, or worse still the JSON string.</p>
  <p>I am testing an AWS Lambda SNS send function but I don't think that's entirely relevant, except to explain the syntax of my system under test:</p>
  <pre><code>export async function handler(event: any) {
      let sns = new SNSClient({ region: REGION });
      let myMessage = {}; // create my message
      let params = {
          Message: JSON.stringify(myMessage),
          TopicArn: 'my ARN'
      }
  
      const result = await sns.send(new PublishCommand(params));
      // some stuff
      return true;
  }
  </code></pre>
  <p>My tests:</p>
  <pre><code>import { handler } from &quot;./handler&quot;;
  import { SNSClient, PublishCommand } from &quot;@aws-sdk/client-sns&quot;;
  
  const mockSnsSend = jest.fn();
  mockSnsSend.mockResolvedValue({ MessageId: 1 });
  jest.mock('@aws-sdk/client-sns', () =&gt; {
      return {
          SNSClient: jest.fn().mockImplementation(() =&gt; {
              return {
                  send: mockSnsSend
              }
          }),
          PublishCommand: jest.fn().mockImplementation()
      }
  });
  
  
  beforeEach(() =&gt; {
      // Clear all instances and calls to constructor and all methods:
      mockSnsSend.mockClear();
      (SNSClient as any).mockClear();
      (PublishCommand as any).mockClear();
      expect.hasAssertions();
  });
  
  
  test('Calls PublishCommand with correct message payload', async () =&gt; {
      let results = await handler(sampleLoraThingMessage);
  
      expect(PublishCommand).toHaveBeenCalledTimes(1);
      // what to do here?
  });
  </code></pre>
  <h2>What I've tried:</h2>
  <h2>expect a string</h2>
  <p>This is easy and passes, but it is not sufficient:</p>
  <pre><code>expect(PublishCommand).toHaveBeenCalledWith(expect.objectContaining({
      Message: expect.any(String)
  }));
  </code></pre>
  <h2>extend expect</h2>
  <p>As per <a href="https://jestjs.io/docs/en/expect#expectextendmatchers" rel="noreferrer">the docs</a> I've tried extending expect to parse the JSON and then run an <code>equals</code> test over it, but I found that this is only available to the <code>expect(a).hasJsonMatchingObject(b)</code> syntax, and I can't use it with <code>toHaveBeenCalledWith</code>.</p>
  <pre><code>expect.extend({
      hasJsonMatchingObject(received, propertyName: string, actual: object) {
          let parsed = JSON.parse(received[propertyName]);
          let pass = this.equals(parsed, actual, undefined, false);
          return {
              pass: pass,
              // todo: I haven't handled the .not() case here yet
              message: () =&gt; `property ${propertyName} must have JSON with certain properties:` +
                  '\n\n' +
                  `Expected: not ${this.utils.printExpected(actual)}\n` +
                  `Received: ${this.utils.printReceived(received)}`
          };
      }
  })
  
  ...
  expect(PublishCommand).toHaveBeenCalledWith(expect.hasJsonMatchingObject(...)); // expect has no property 'hasJsonMatchingObject'
  </code></pre>