I am using the code snippet provided in below URL for instantiating ResourceResponse for unit test mocking purpose
https://github.com/Azure/azure-cosmosdb-dotnet/issues/342#issuecomment-367827999
But I am getting below error at given line:
var documentServiceResponse = Activator.CreateInstance(documentServiceResponseType, flags, null, arguments, null);
System.MissingMethodException: 'Constructor on type
'Microsoft.Azure.Documents.DocumentServiceResponse' not found.'
Ultimately I want to mock Response properties like RequestCharge.
Please suggest how t achieve that.
Thanks in advance
You can do that by adding Cosmonaut's TestingExtensions
Here is an extension method that convert any object to a ResourceReponse.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=1.9.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var headers = new NameValueCollection { { "x-ms-request-charge", "0" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var arguments = new object[] { Stream.Null, headers, statusCode, null };
var documentServiceResponse =
documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", BindingFlags.NonPublic | BindingFlags.Instance);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
This will only work for pre-2.0.0 SDK versions.
For post 2.0.0 use this one instead.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var headers = new NameValueCollection { { "x-ms-request-charge", "0" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var headersDictionaryType = Type.GetType("Microsoft.Azure.Documents.Collections.DictionaryNameValueCollection, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var headersDictionaryInstance = Activator.CreateInstance(headersDictionaryType, headers);
var arguments = new [] { Stream.Null, headersDictionaryInstance, statusCode, null };
var documentServiceResponse = documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", flags);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
You can read more about CosmosDB C# code unit testing here
Related
I want to export a CookieContainer to JSON using Newtonsoft.Json but unfortunately CookieContainer hasn't an enumerator or stuff, so I can't cycle through it ...
Edit: With my posted solution it would be something like this:
private static void Main(string[] args)
{
CookieContainer cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("name1", "value1", "/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/path2/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name1", "value1", "/", ".testdomain2.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/", ".testdomain2.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/path2/", ".testdomain2.com"));
CookieCollection cookies = GetAllCookies(cookieContainer);
Console.WriteLine(JsonConvert.SerializeObject(cookies, Formatting.Indented));
Console.Read();
}
A solution using reflection:
public static CookieCollection GetAllCookies(CookieContainer cookieJar)
{
CookieCollection cookieCollection = new CookieCollection();
Hashtable table = (Hashtable) cookieJar.GetType().InvokeMember("m_domainTable",
BindingFlags.NonPublic |
BindingFlags.GetField |
BindingFlags.Instance,
null,
cookieJar,
new object[] {});
foreach (var tableKey in table.Keys)
{
String str_tableKey = (string) tableKey;
if (str_tableKey[0] == '.')
{
str_tableKey = str_tableKey.Substring(1);
}
SortedList list = (SortedList) table[tableKey].GetType().InvokeMember("m_list",
BindingFlags.NonPublic |
BindingFlags.GetField |
BindingFlags.Instance,
null,
table[tableKey],
new object[] { });
foreach (var listKey in list.Keys)
{
String url = "https://" + str_tableKey + (string) listKey;
cookieCollection.Add(cookieJar.GetCookies(new Uri(url)));
}
}
return cookieCollection;
}
.NET 6 Update
Finally, .NET 6 was released and introduced the CookieContainer.GetAllCookies() method which extracts the CookieCollection - Documentation link.
public System.Net.CookieCollection GetAllCookies();
This method will ensure to get all cookies, no matter what the protocol is:
public static IEnumerable<Cookie> GetAllCookies(this CookieContainer c)
{
Hashtable k = (Hashtable)c.GetType().GetField("m_domainTable", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(c);
foreach (DictionaryEntry element in k)
{
SortedList l = (SortedList)element.Value.GetType().GetField("m_list", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(element.Value);
foreach (var e in l)
{
var cl = (CookieCollection)((DictionaryEntry)e).Value;
foreach (Cookie fc in cl)
{
yield return fc;
}
}
}
}
The first answer did not work for a portable project. So here's option 2, also uses reflection
using System.Linq;
using System.Collections;
using System.Reflection;
using System.Net;
public static CookieCollection GetAllCookies(this CookieContainer container)
{
var allCookies = new CookieCollection();
var domainTableField = container.GetType().GetRuntimeFields().FirstOrDefault(x => x.Name == "m_domainTable");
var domains = (IDictionary)domainTableField.GetValue(container);
foreach (var val in domains.Values)
{
var type = val.GetType().GetRuntimeFields().First(x => x.Name == "m_list");
var values = (IDictionary)type.GetValue(val);
foreach (CookieCollection cookies in values.Values)
{
allCookies.Add(cookies);
}
}
return allCookies;
}
1) I also tried
var domainTableField = container.GetType().GetRuntimeField("m_domainTable");
but it returned null.
2) You can iterate through domains.Keys and use container.GetCookies() for all keys. But I've had problems with that, because GetCookies expects Uri and not all my keys matched Uri pattern.
Use CookieContainer.GetCookies Method
CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
where url is URL of your site.
In my case, I was not able to use reflection, as suggested in other answers. However, I did know URL of my site to query. I think it is even logical that container does not return all cookies blindly but returns them per URL because cookies always belong to a particular URL and cannot be used outside of context of the domain associated with them.
I am trying to retrieve the TLS Version information. The code I have below makes a successful HTTP GET call using HttpClient. What am I missing? Where do I get the TLS Version information from HttpClient?
I am kind of doing the same thing as was suggested in Which TLS version was negotiated? but that is specific to WebRequest which is not the same as HttpClient.
static async Task MainAsync()
{
Uri baseURI = new Uri("https://jsonplaceholder.typicode.com/posts/1");
string apiPath = "";
using (var client = new HttpClient())
{
client.BaseAddress = baseURI;
HttpResponseMessage response = await client.GetAsync(apiPath);
Console.WriteLine("HTTP status code: " + response.StatusCode.ToString());
GetSSLConnectionInfo(response, client.BaseAddress.ToString(), apiPath);
}
Console.ReadKey();
}
static async Task GetSSLConnectionInfo(HttpResponseMessage response, string baseURI, string apiPath)
{
using (Stream stream = await response.RequestMessage.Content.ReadAsStreamAsync())
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
Stream CompressedStream = null;
if (stream.GetType().BaseType == typeof(GZipStream))
{
CompressedStream = (GZipStream)stream;
}
else if (stream.GetType().BaseType == typeof(DeflateStream))
{
CompressedStream = (DeflateStream)stream;
}
var objbaseStream = CompressedStream?.GetType().GetProperty("BaseStream").GetValue(stream);
if (objbaseStream == null)
{
objbaseStream = stream;
}
var objConnection = objbaseStream.GetType().GetField("m_Connection", bindingFlags).GetValue(objbaseStream);
var objTlsStream = objConnection.GetType().GetProperty("NetworkStream", bindingFlags).GetValue(objConnection);
var objSslState = objTlsStream.GetType().GetField("m_Worker", bindingFlags).GetValue(objTlsStream);
SslProtocols b = (SslProtocols)objSslState.GetType().GetProperty("SslProtocol", bindingFlags).GetValue(objSslState);
Console.WriteLine("SSL Protocol Used for " + baseURI + apiPath + System.Environment.NewLine + "The TLS version used is " + b);
}
}
I am expecting TLS connection Info but I get an exception.
Under the hood HttpClient uses internal TlsStream class (as in your example for WebRequest). We just need to find it in another location. Here is an example:
static void Main(string[] args)
{
using (var client = new HttpClient())
{
using (var response = client.GetAsync("https://example.com/").Result)
{
if (response.Content is StreamContent)
{
var webExceptionWrapperStream = GetPrivateField(response.Content, "content");
var connectStream = GetBasePrivateField(webExceptionWrapperStream, "innerStream");
var connection = GetPrivateProperty(connectStream, "Connection");
var tlsStream = GetPrivateProperty(connection, "NetworkStream");
var state = GetPrivateField(tlsStream, "m_Worker");
var protocol = (SslProtocols)GetPrivateProperty(state, "SslProtocol");
Console.WriteLine(protocol);
}
else
{
// not sure if this is possible
}
}
}
}
private static object GetPrivateProperty(object obj, string property)
{
return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
private static object GetPrivateField(object obj, string field)
{
return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
private static object GetBasePrivateField(object obj, string field)
{
return obj.GetType().BaseType.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
Just adjusting the old code to make it work on current dotnet 6.0 version.
using System.Net;
using System.Reflection;
using System.Security.Authentication;
public static class Program
{
static void Main(string[] args)
{
using (var client = new WebClient())
{
var stm = client.OpenRead("https://www.google.com");
var connection = GetField(stm, "_connection");
var transportContext = GetProperty(connection, "TransportContext");
var sslStream = GetField(transportContext, "_sslStream");
var protocol = (SslProtocols)GetProperty(sslStream, "SslProtocol");
Console.WriteLine(protocol);
stm.Close();
}
}
private static object GetProperty(object obj, string property)
{
return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
}
private static object GetField(object obj, string field)
{
return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
}
}
I have a parent object that contains a CancellationTokenSource. This object passes its CancellationToken into a process that communicates sequentially with external services. Whenever a call is made to an external service, the CancellationToken is registered to a method that will allow the process to stop waiting for the external service to respond:
myObj.CancellationTokenRegistration.Dispose();
myObj.CancellationTokenRegistration = myObj.CancellationToken.Register(() => CancelMethod(myObj));
Given only the CancellationTokenSource, is there a way to know that a cancellation method has been registered against the token?
Without using reflection (or other types of voodoo) to look at the internal state of the CancellationTokenSource there's no way to know if some code has added any registrations.
If you do want to use reflection you should look at this field:
private volatile SparselyPopulatedArray<CancellationCallbackInfo>[] m_registeredCallbacksLists;
The m_callbackInfo field seems to contain the same information.
CancellationTokenSource cts = new CancellationTokenSource();
Action test = CancelMethod;
CancellationTokenRegistration = cts.Token.Register(test);
var fieldInfo = typeof(CancellationTokenRegistration).GetField("m_callbackInfo", BindingFlags.NonPublic | BindingFlags.Instance);
object fieldValue = fieldInfo.GetValue(CancellationTokenRegistration);
var callbackFieldInfo = fieldValue.GetType().GetField("Callback", BindingFlags.Instance | BindingFlags.NonPublic);
var callbackValue = callbackFieldInfo.GetValue(fieldValue);
var stateForCallbackFieldInfo = fieldValue.GetType().GetField("StateForCallback", BindingFlags.Instance | BindingFlags.NonPublic);
var stateForCallbackValue = stateForCallbackFieldInfo.GetValue(fieldValue);
// stateForCallbackValue == CancelMethod; if Token.Register is called with one of the Action<object> arguments
// callbackValue == CancelMethod
private void CancelMethod()
{
throw new System.NotImplementedException();
}
Here is some rough code using reflection to enumerate registered Actions (works as expected with .Net 4.7.1):
public static IEnumerable<Action<object>> Registrations(this CancellationToken token)
{
var sourceFieldInfo = typeof(CancellationToken).GetField("m_source", BindingFlags.NonPublic | BindingFlags.Instance);
var cancellationTokenSource = (CancellationTokenSource)sourceFieldInfo.GetValue(token);
var callbacksFieldInfo = typeof(CancellationTokenSource).GetField("m_registeredCallbacksLists", BindingFlags.NonPublic | BindingFlags.Instance);
var callbaskLists = (Array)callbacksFieldInfo.GetValue(cancellationTokenSource);
foreach (var sparselyPopulatedArray in callbaskLists)
{
if (sparselyPopulatedArray == null)
{
continue;
}
var sparselyPopulatedArrayType = sparselyPopulatedArray.GetType();
var tailFieldInfo = sparselyPopulatedArrayType.GetProperty("Tail", BindingFlags.NonPublic | BindingFlags.Instance);
var tail = tailFieldInfo.GetValue(sparselyPopulatedArray);
var sparselyPopulatedArrayFragmentType = tail.GetType();
var elementsTypeFieldInfo = sparselyPopulatedArrayFragmentType.GetField("m_elements", BindingFlags.NonPublic | BindingFlags.Instance);
var elements = (Array)elementsTypeFieldInfo.GetValue(tail);
foreach (var callbackInfo in elements)
{
if (callbackInfo == null)
{
continue;
}
var callbackInfoType = callbackInfo.GetType();
var callbackFieldInfo = callbackInfoType.GetField("Callback", BindingFlags.NonPublic | BindingFlags.Instance);
var callback = (Action<object>)callbackFieldInfo.GetValue(callbackInfo);
if (callback != null)
{
yield return callback;
}
}
}
}
I finally was able to get the HttpContext.Current to be not null by finding some code online. But I still have not be able to add custom headers to the request in my unit test. Here is my test:
[TestClass]
public class TagControllerTest
{
private static Mock<IGenericService<Tag>> Service { get; set; }
private TagController controller;
[TestInitialize]
public void ThingServiceTestSetUp()
{
Tag tag = new Tag(1, "people");
Response<Tag> response = new Response<Tag>();
response.PayLoad = new List<Tag>() { tag };
Service = new Mock<IGenericService<Tag>>(MockBehavior.Default);
Service.Setup(s => s.FindAll("username", "password", "token")).Returns(response);
controller = new TagController(Service.Object);
HttpContext.Current = FakeHttpContext();
}
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://kindermusik/", "");
var stringWriter = new StringWriter();
var httpResponce = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponce);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
httpContext.Request.Headers["username"] = "username"; //It throws a PlatformNotSupportedException exception
httpContext.Request.Headers["password"] = "password"; //.Headers.Add("blah", "blah") throws same error
httpContext.Request.Headers["token"] = "token"; //And so to .Headers.Set("blah", "blah")
return httpContext;
}
[TestMethod]
public void TagControllerGetTest()
{
// Arrange
Response<Tag> result = controller.Get();
// Assert
Assert.AreEqual(true, result.IsSuccess);
Assert.AreEqual(1, result.PayLoad.Count);
Assert.AreEqual("people", result.PayLoad[0].Name);
}
This is the code that is being tested.
public class TagController : ApiController
{
public IGenericService<Tag> _service;
public TagController()
{
_service = new TagService();
}
public TagController(IGenericService<Tag> service)
{
this._service = service;
}
// GET api/values
public Response<Tag> Get()
{
HttpContext context = HttpContext.Current;
string username = context.Request.Headers["username"].ToString();
string password = context.Request.Headers["password"].ToString();
string token = context.Request.Headers["token"].ToString();
return (Response<Tag>) _service.FindAll(username, password, token);
}
}
You can use this, it worked with:
Setting HttpContext.Current.Session in a unit test
User Anthony's answer, and add this code in GetMockedHttpContext:
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
then you can add:
HttpContextFactory.Current.Request.Headers.Add(key, value);
by this you can post headers. But unfortunately you have to use HttpContextFactory instead of HttpContext
Thanks to Adam Reed's blog it is possible to modify Headers collection using reflexion : MOCK HTTPCONTEXT.CURRENT.REQUEST.HEADERS UNIT TEST
HttpContext.Current = new HttpContext(
new HttpRequest("", "http://tempuri.org", ""), new HttpResponse(new StringWriter()));
NameValueCollection headers = HttpContext.Current.Request.Headers;
Type t = headers.GetType();
const BindingFlags nonPublicInstanceMethod = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance;
t.InvokeMember("MakeReadWrite", nonPublicInstanceMethod, null, headers, null);
t.InvokeMember("InvalidateCachedArrays", nonPublicInstanceMethod, null, headers, null);
// eg. add Basic Authorization header
t.InvokeMember("BaseRemove", nonPublicInstanceMethod, null, headers, new object[] { "Authorization" });
t.InvokeMember("BaseAdd", nonPublicInstanceMethod, null, headers,
new object[] { "Authorization", new ArrayList{"Basic " + api_key} });
t.InvokeMember("MakeReadOnly", nonPublicInstanceMethod, null, headers, null);
I believe that in the API controller methods you can use "Request" property:
var testValue = this.Request.Headers.GetValues("headerKey").FirstOrDefault();
And then you can add test values in your unit tests this way:
var controller = new TestController();
controller.Request = new HttpRequestMessage();
controller.Request.Headers.Add("headerKey", "testValue");
I have the following code to test that when a certain name is passed to my method, it throws a SQL exception (there is reason to that one, although it sounds a little odd).
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(),
"Display Name 2", It.IsAny<string>())).Throws<SqlException>();
However, this won't compile because SqlException's constructor is internal:
'System.Data.SqlClient.SqlException' must be a non-abstract type with
a public parameterless constructor in order to use it as parameter
'TException' in the generic type or method
'Moq.Language.IThrows.Throws()'
Now, I could change this to state that it should throw Exception, but that wouldn't work for me, because my method should return one status code if it is a SqlException and another if it is any other exception. That's what my unit test is testing.
Is there any way to achieve this without either changing the logic of the method I'm testing, or not testing this scenario?
If you need test cases for the Number or Message properties of the exception, you could use a builder (which uses reflection) like this:
using System;
using System.Data.SqlClient; // .NetCore using Microsoft.Data.SqlClient;
using System.Linq;
using System.Reflection;
public class SqlExceptionBuilder
{
private int errorNumber;
private string errorMessage;
public SqlException Build()
{
SqlError error = this.CreateError();
SqlErrorCollection errorCollection = this.CreateErrorCollection(error);
SqlException exception = this.CreateException(errorCollection);
return exception;
}
public SqlExceptionBuilder WithErrorNumber(int number)
{
this.errorNumber = number;
return this;
}
public SqlExceptionBuilder WithErrorMessage(string message)
{
this.errorMessage = message;
return this;
}
private SqlError CreateError()
{
// Create instance via reflection...
var ctors = typeof(SqlError).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
var firstSqlErrorCtor = ctors.FirstOrDefault(
ctor =>
ctor.GetParameters().Count() == 7); // .NetCore should be 8 not 7
SqlError error = firstSqlErrorCtor.Invoke(
new object[]
{
this.errorNumber,
new byte(),
new byte(),
string.Empty,
string.Empty,
string.Empty,
new int()
//,new Exception() // for .NetCore
}) as SqlError;
return error;
}
private SqlErrorCollection CreateErrorCollection(SqlError error)
{
// Create instance via reflection...
var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
SqlErrorCollection errorCollection = sqlErrorCollectionCtor.Invoke(new object[] { }) as SqlErrorCollection;
// Add error...
typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(errorCollection, new object[] { error });
return errorCollection;
}
private SqlException CreateException(SqlErrorCollection errorCollection)
{
// Create instance via reflection...
var ctor = typeof(SqlException).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
SqlException sqlException = ctor.Invoke(
new object[]
{
// With message and error collection...
this.errorMessage,
errorCollection,
null,
Guid.NewGuid()
}) as SqlException;
return sqlException;
}
}
Then you could have a repository mock (for instance) throw an exception like this (this example uses the Moq library):
using Moq;
var sqlException =
new SqlExceptionBuilder().WithErrorNumber(50000)
.WithErrorMessage("Database exception occured...")
.Build();
var repoStub = new Mock<IRepository<Product>>(); // Or whatever...
repoStub.Setup(stub => stub.GetById(1))
.Throws(sqlException);
This should work:
using System.Runtime.Serialization;
var exception = FormatterServices.GetUninitializedObject(typeof(SqlException))
as SqlException;
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>(), "Display Name 2",
It.IsAny<string>())).Throws(exception);
However, using GetUninitializedObject has this caveat:
Because the new instance of the object is initialized to zero and no
constructors are run, the object might not represent a state that is
regarded as valid by that object.
If this causes any problems, you can probably create it using some more involved reflection magic but this way is probably the simplest (if it works).
I just tried this out, and it worked for me:
private static void ThrowSqlException()
{
using (var cxn = new SqlConnection("Connection Timeout=1"))
{
cxn.Open();
}
}
// ...
mockAccountDAL.Setup(m => m.CreateAccount(It.IsAny<string>),
"Display Name 2", It.IsAny<string>()))
.Callback(() => ThrowSqlException());
For me to produce an SqlException with a message it was the simplest way using the Uninitialized Object method:
const string sqlErrorMessage = "MyCustomMessage";
var sqlException = FormatterServices.GetUninitializedObject(typeof(SqlException)) as SqlException;
var messageField = typeof(SqlException).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance);
messageField.SetValue(sqlException, sqlErrorMessage);
I wrote this before finding this question/answer. Might be useful for someone just wanting a SQL exception with a particular number.
private static SqlException CreateSqlExceptionWithNumber(int errorNumber)
{
var sqlErrorCollectionCtor = typeof(SqlErrorCollection).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Any,
new Type[0],
null);
var sqlErrorCollection = (SqlErrorCollection)sqlErrorCollectionCtor.Invoke(new object[0]);
var errors = new ArrayList();
var sqlError = (SqlError)FormatterServices.GetSafeUninitializedObject(typeof(SqlError));
typeof(SqlError)
.GetField("number", BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(sqlError, errorNumber);
errors.Add(sqlError);
typeof(SqlErrorCollection)
.GetField("errors", BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(sqlErrorCollection, errors);
var exception = (SqlException)FormatterServices.GetUninitializedObject(typeof(SqlException));
typeof(SqlException)
.GetField("_errors", BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(exception, sqlErrorCollection);
return exception;
}
public class SqlExceptionMock
{
public static SqlException ThrowSqlException(int errorNumber, string message = null)
{
var ex = (SqlException)FormatterServices.GetUninitializedObject(typeof(SqlException));
var errors = GenerateSqlErrorCollection(errorNumber, message);
SetPrivateFieldValue(ex, "_errors", errors);
return ex;
}
private static SqlErrorCollection GenerateSqlErrorCollection(int errorNumber, string message)
{
var t = typeof(SqlErrorCollection);
var col = (SqlErrorCollection)FormatterServices.GetUninitializedObject(t);
SetPrivateFieldValue(col, "_errors", new List<object>());
var sqlError = GenerateSqlError(errorNumber, message);
var method = t.GetMethod(
"Add",
BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(col, new object[] { sqlError });
return col;
}
private static SqlError GenerateSqlError(int errorNumber, string message)
{
var sqlError = (SqlError)FormatterServices.GetUninitializedObject(typeof(SqlError));
SetPrivateFieldValue(sqlError, "_number", errorNumber);
if (!string.IsNullOrEmpty(message)) SetPrivateFieldValue(sqlError, "_message", message);
return sqlError;
}
private static void SetPrivateFieldValue(object obj, string field, object val)
{
var member = obj.GetType().GetField(
field,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
);
member?.SetValue(obj, val);
}
}