Repository: lundberg/respx

Issue:
None type doesn't work in data matching as of v0.21.0
As of the latest version `v0.21.0`, None values in data lookups don't work:

```python
import httpx
import respx


@respx.mock
def test():
    data = {"test": None}

    respx.post("http://test.com", data=data).respond()

    response = httpx.post("http://test.com", data=data)

    assert response.status_code == 200


if __name__ == "__main__":
    test()
```
output:
```python
respx.models.AllMockedAssertionError: RESPX: <Request('POST', 'http://test.com/')> not mocked!
```

This test passes successfully in `v0.20.2`


The following _do_ work:
```python
    data = {"test": ""}
    respx.post("http://test.com", data=data).respond()
    response = httpx.post("http://test.com", data=data)
```

```python 
    respx.post("http://test.com", data={"test": ""}).respond()
    response = httpx.post("http://test.com", data={"test": None})
```

So there's just a bug somewhere translating None to ""



Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/lundberg/respx/pull/259?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Lundberg) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`15522db`)](https://app.codecov.io/gh/lundberg/respx/commit/15522db36d6a08f2c062831ec664df2b9d2e1f69?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Lundberg) to head [(`39aa598`)](https://app.codecov.io/gh/lundberg/respx/pull/259?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Lundberg).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##            master      #259    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           22        22            
  Lines         2910      2910            
  Branches       443       329   -114     
==========================================
  Hits          2910      2910            
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/lundberg/respx/pull/259?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Lundberg).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jonas+Lundberg).

pierremonico: Not sure if it's related but I also encounter issues when I run (as a `unittest.TestCase` method):
```py
    @respx.mock
    def test_file_upload(self):

        FILE_DATA = {
            "upload": ("image/png", io.BytesIO(b"some image content"), "image/png")
        }

        respx.patch("https://api.example.com/endpoint/123", files=FILE_DATA)

        httpx.patch("https://api.example.com/endpoint/123", files=FILE_DATA)
```

This raises:
```bash
raise AllMockedAssertionError(f"RESPX: {request!r} not mocked!")
respx.models.AllMockedAssertionError: RESPX: <Request('PATCH', 'https://api.example.com/endpoint/123')> not mocked!
```

Patch:
diff --git a/respx/patterns.py b/respx/patterns.py
index d8b00b5..8d80148 100644
--- a/respx/patterns.py
+++ b/respx/patterns.py
@@ -548,7 +548,9 @@ class Data(MultiItemsMixin, Pattern):
     value: MultiItems
 
     def clean(self, value: Dict) -> MultiItems:
-        return MultiItems(value)
+        return MultiItems(
+            (key, "" if value is None else str(value)) for key, value in value.items()
+        )
 
     def parse(self, request: httpx.Request) -> Any:
         data, _ = decode_data(request)
diff --git a/respx/utils.py b/respx/utils.py
index 5a6ce3a..5eb4715 100644
--- a/respx/utils.py
+++ b/respx/utils.py
@@ -1,7 +1,18 @@
 import email
 from datetime import datetime
 from email.message import Message
-from typing import Dict, List, NamedTuple, Optional, Tuple, Type, TypeVar, Union, cast
+from typing import (
+    Any,
+    Dict,
+    List,
+    NamedTuple,
+    Optional,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+    cast,
+)
 from urllib.parse import parse_qsl
 
 try:
@@ -13,13 +24,13 @@ import httpx
 
 
 class MultiItems(dict):
-    def get_list(self, key: str) -> List[str]:
+    def get_list(self, key: str) -> List[Any]:
         try:
             return [self[key]]
         except KeyError:  # pragma: no cover
             return []
 
-    def multi_items(self) -> List[Tuple[str, str]]:
+    def multi_items(self) -> List[Tuple[str, Any]]:
         return list(self.items())
 
 


Test patch:
diff --git a/tests/test_patterns.py b/tests/test_patterns.py
index dda7914..4b119aa 100644
--- a/tests/test_patterns.py
+++ b/tests/test_patterns.py
@@ -350,6 +350,18 @@ def test_content_pattern(lookup, content, expected):
             None,
             True,
         ),
+        (
+            Lookup.EQUAL,
+            {"none_value": None},
+            None,
+            True,
+        ),
+        (
+            Lookup.EQUAL,
+            {"non_str": 123},
+            None,
+            True,
+        ),
         (
             Lookup.EQUAL,
             {"x": "a"},