I recently started learning how to write unit tests, and what part of the unit to test for functionality and what to mock out. I'm using NSubstitute as my mocking framework. My example basically calls a repo class, which then makes an WEB API web call to an external service, ie: AddCreditCard, which then returns a result. I created 2 unit tests for AddCreditCard, one for Success, and one for Fail. I'm still not 100% sure I'm doing all of this correctly. The unit tests are passing, but im not sure if my Asserts are done on the correct data... All help and suggestions welcome!
public interface IApiResponse<T>
{
HttpStatusCode StatusCode { get; set; }
T Result { get; set; }
string ErrorMessage { get; }
bool HasErrors { get; }
}
public interface ISignedRequest
{
void ConfigureSettings(SignedRequestSettings settings);
IApiResponse Post(string jsonData, Dictionary<string, string> parameters = null,
IOptionalHeaders optionalHeaders = null);
}
public class PaymentRepository
{
private readonly SignedRequestSettings _settings;
private readonly ISignedRequest _signedRequest;
public PaymentRepository(ISignedRequest signedRequest = null)
{
if (signedRequest == null)
_signedRequest = new SignedRequest();
else
_signedRequest = signedRequest;
}
public IApiResponse AddCreditCard(CreditCard request)
{
var jsonData =
JsonConvert.SerializeObject(request);
string action = string.Format("customers/{0}/paymentmethods", request.ConnectId);
_settings.Url = string.Format("{0}{1}", String.Empty, action);
_signedRequest.ConfigureSettings(_settings);
return _signedRequest.Post(jsonData);
}
}
[Test]
public void AddCreditCard_GivenValidCreditCard_ReturnsCreatedResult()
{
//Given
var apiResponse = Substitute.For<IApiResponse>();
apiResponse.StatusCode = HttpStatusCode.Created;
var signedRequest = Substitute.For<ISignedRequest>();
signedRequest.Post(Arg.Any<String>()).Returns(apiResponse);
var creditCard = Substitute.For<CreditCard>();
creditCard.ConnectId = Guid.Parse("1fc1ad83-cd4e-4b68-bce6-e03ee8f47fb6");
var repo = new PaymentRepository(signedRequest);
//When
var addCreditCardResponse = repo.AddCreditCard(creditCard);
//Then
signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
signedRequest.Received(1).Post(Arg.Any<String>());
Assert.AreEqual(HttpStatusCode.Created, addCreditCardResponse.StatusCode);
}
[Test]
public void AddCreditCard_GivenInvalidCreditCard_ReturnsHasErrorsResult()
{
//Given
var apiResponse = Substitute.For<IApiResponse>();
apiResponse.HasErrors.Returns(true);
apiResponse.ErrorMessage.Returns("Add credit card error message");
var signedRequest = Substitute.For<ISignedRequest>();
signedRequest.Post(Arg.Any<String>()).Returns(apiResponse);
var creditCard = Substitute.For<CreditCard>();
creditCard.ConnectId = Guid.Parse("1fc1ad83-cd4e-4b68-bce6-e03ee8f47fb6");
var repo = new PaymentRepository(signedRequest);
//When
var addCreditCardResponse = repo.AddCreditCard(creditCard);
//Then
signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
signedRequest.Received(1).Post(Arg.Any<String>());
Assert.AreEqual(apiResponse.HasErrors, addCreditCardResponse.HasErrors);
Assert.AreEqual(apiResponse.ErrorMessage, addCreditCardResponse.ErrorMessage);
}
I think your tests are mostly OK, but there are some bits I would question. Both of your tests have these lines at the bottom:
signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
signedRequest.Received(1).Post(Arg.Any<String>());
Is it really important to those tests that these methods have been called on your signedRequest substitute? I'd suggest that it probably isn't. If these calls weren't made I'd argue that your test would have failed anyway (although this is to some extent a stylistic decision).
The second thing I'd say is that you're missing one or more tests (again the number is a bit stylistic). As it stands, you're not validating the information that's being supplied to the ConfigureSettings or Post calls on your signedRequest substitute. Your repository code could simply be doing _signedRequest.Post("some random string"); and your test would still pass. I'd add another test that validates these calls to ensure that the request is actually sent correctly. This is where the Received validation would make sense. Something like:
signedRequest.Received(1).ConfigureSettings(Arg.Is<SignedRequestSettings>(x=>x.Url == someHardCodedUrl));
signedRequest.Received(1).Post(someHardCodedJsonString);
Related
I got an error related with security when I tried to deserialize by using `System.Text.Json JsonSerializer`.
What do I want to achieve?
I want to give the user controle to transalte some records in my database, so use can follow this scenario:
1- User can choose model of my class library.
2- After selecting a class, user will select a property(filed) from this class.
3- User will get list of values of the selected property up.
4- Last step is not here right now, user can edit a certian value.
This my piece of code:
MyPage.razor.cs:
[Inject]
private IGenericHttpClient<Type> HttpClient { get; set; }
private Type SelectedType { get; set; }
// First select a class [Class library] from HTML Select
private void OnTypeChnage(ChangeEventArgs args)
{
string FullName = "My.Models." + args.Value.ToString();
// Create type of selected class
SelectedType = Assemble.GetType(FullName, false);
}
//Call api to get all fields of this class
private async Task OnPropertChange(ChangeEventArgs args)
{
var list = await
HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
}
GenericHttpClient.cs
public async ValueTask<List<T>> GetJsonAsync(string url)
{
using HttpResponseMessage response = await _client.GetAsync(url);
ValidateResponse(response);
var conetnt = await response.Content.ReadAsStringAsync();
//I got the error down
return JsonSerializer.Deserialize<List<T>>(conetnt, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
System.Text.Json does not support Type class due to security reasons. You send the full assembly name as a string and again try to construct the Type at the client end.
public async ValueTask<List<T>> GetJsonAsync(string url) this wont even compile, due to not specify generic information on method signature.
And also, your problem would come from the content of http response, otherwise, the Deserialize step should work fine.
I copied your code and make a small block that prove it.
// Define somewhere
public class GenericHttpClient
{
public List<T> GetJsonAsync<T>()
{
var content = "[{\"TestProp\": \"This is some test\"}]";
return JsonSerializer.Deserialize<List<T>>(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
}
public class Test
{
public string TestProp { get; set; }
}
// Test it
var test = new GenericHttpClient();
var result = test.GetJsonAsync<Test>();
Like what #Mayur Ekbote mentioned up, "System.Text.Json does not support Type class due to security reasons." I will add a solution but I don't think this solution is very efficient.
Change Type to Dynamic:
[Inject]
private IGenericHttpClient<dynamic> HttpClient { get; set; }
Use JsonElement to get the value as a string:
private async Task OnPropertChange(ChangeEventArgs args)
{
var langCode = CultureInfo.CurrentCulture.Name;
PropertyValueList.Clear();
var list = await HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
List<object> listValue = new List<object>();
SelectedProperty = args.Value.ToString();
string fieldName = char.ToLower(SelectedProperty[0]) + SelectedProperty.Substring(1);
foreach (var item in list)
{
//Convert object to JsonElement
var val = ((JsonElement)item).GetProperty(fieldName).GetString();
PropertyValueList.Add(val);
}
}
Why is it not efficient?
Because I got a list of value String instead of list of selected class.
I have using RestSharp to test APIs and I have a delete call that I want to run twice in the same method.
The delete call will delete with two different query params. It only can take one query param at a time so I want to call it twice with the two different query params. What is the best optimized solution to this.
The example below I am deleting with the user id 1 and I want to also delete with user id 2
[Test]
public void DeletedUser()
{
response = HttpDelete("url");
QueryParam.Add("id", 1);
Assert.statusCode(200, response);
}
I have used Andy solution to use TestCase attribute but I get an syntax error when trying to not hard code the data being used.
Error Message: "An attribute argument must be a constant expression , typeof expression or array creation expression of an attribute parameter type"
ex..
public static string data = "1"
[TestCase(data)] //Getting the error here
[Test]
public void DeletedUser(string id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
I need to run the call using two dynamic test data. The data gets generated from a Post call before the Delete call and it gets saved and serialized into a class where I have the data variables..
Here is an example of the class where the test data is stored
public class Data
{
public class UserData
{
public string id1;
public string id2;
public UserData()
{
id1 = "";
id2 = "";
}
}
}
This is the Post call and how the data is being saved.
[Test]
public void AddUser()
{
response = HttpPost("url", modle);
Data data = new Data()
data.UserData.id1 = response.content;
}
How can I now use this data.UserData.id1 in my TestCase attribute
You can make use of NUnit's [TestCase] attribute to run a test multiple times with different parameters.
Example
[TestCase(1)]
[TestCase(2)]
public void DeletedUser(int id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
You can extend this with as many parameters as needed to complete a test. For example, if you expect different responses for different IDs:
[TestCase(1, 200)]
[TestCase(2, 404)]
public void DeletedUser(int id, int expectedResponseCode)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(expectedResponseCode, response);
}
Full documentation is available here.
Update
In response to your further question about testing with dynamic data, as Charlie said you can only reference literals or literal constants from an attribute so you won't be able to use [TestCase] with dynamic data.
Instead you could use the [TestCaseSource] attribute. Create a static method that retrieves your test data and returns an array, and then quote the name of this method in the attribute.
private static int[] GetIdsToDelete() {
// UserData userData = ... read in data
return new int[] {
userData.id1,
userData.id2
}
}
[TestCaseSource(nameof(GetIdsToDelete))]
public void DeletedUser(int id)
{
response = HttpDelete("url");
QueryParam.Add("id", id);
Assert.statusCode(200, response);
}
You can find the full documentation for TestCaseSource here.
I was creating a couple of Unit Tests where I wanted to verify if a method is called with a parameter whose properties I was expecting.
So given this very simple system:
public class Employee
{
public bool IsEmployed { get; set; }
}
public class DataStore
{
public void UpdateEmployee(Employee obj)
{
// Save in DB
}
}
public interface IDataStore
{
void UpdateEmployee(Employee employee);
}
public Employee FireEmployee(IDataStore dataStore, Employee employee)
{
employee.IsEmployed = false;
dataStore.UpdateEmployee(employee);
return employee;
}
I want to verify that the DataStore.UpdateEmployee() method is called when the Employee.IsEmployed property is set to false. So here are two test cases that I believe should accomplish the same thing.
[Test]
public void TestViaVerify()
{
//Arrange
Mock<IDataStore> dataStore = new Mock<IDataStore>();
var robert = new Employee { IsEmployed = true };
//Act
FireEmployee(dataStore.Object, robert);
//Assert
dataStore.Verify(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)), Times.Once);
}
[Test]
public void TestViaSetupVerifyAll()
{
//Arrange
Mock<IDataStore> dataStore = new Mock<IDataStore>();
dataStore.Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)));
var robert = new Employee { IsEmployed = true };
//Act
FireEmployee(dataStore.Object, robert);
//Assert
dataStore.VerifyAll();
}
Given the current code of the system, both test passes as expected.
Now say another developer comes along and accidentally moved the setting of the Employee.IsEmployed = false; after the DataStore.UpdateEmployee() method. Now in that case, I want my tests to fail because the employee will not be marked as unemployed in the DB.
public Employee FireEmployee(IDataStore dataStore, Employee employee)
{
dataStore.UpdateEmployee(employee);
employee.IsEmployed = false;
return employee;
}
Now when I run the test:
TestViaVerify Passes
TestViaSetupVerifyAll Fails
I was expecting both of them to fail but it looks like for the TestViaVerify() method, the lambda in the method is executed at the end of the test wherein the Employee.IsEmployed is already set to false.
Is there a way to accomplish what I want with just using the Verify method? And not have to do Setup...VerifyAll? If there is none I'll just go with TestViaVerifyAll() approach.
To be honest it is been more then 2 years since the last time I was updated with moq source code. Both Verify and VerifyAll are based on that every invocation of the fake instance is being captured(include the parameters).
Verify will looking for method/property invocation and verifies the captured invocations(with their captured parameters) while, VerifyAll will take all the setup methods and do the same as Verify method.
Since the captured parameter is ByRef parameter and if the last paragraph is still relevant you can cause your UTs failed simply by add robert.IsEmployed = true; before invoke Verify/VerifyAll:
[Test]
public void TestViaVerify()
{
....
robert.IsEmployed = true; // will make this UT to failed
//Assert
dataStore.Verify(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)), Times.Once);
}
[Test]
public void TestViaSetupVerifyAll()
{
....
robert.IsEmployed = true; // will make this UT to failed
//Assert
dataStore.VerifyAll();
}
I think that in the past I answered something similar(with more examples), the way I was workaround this problem is a combination between Setup and Callback since I don't like to use VerifyAll pattern:
....
var invokedCorrectly = false;
dataStore.Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)))
.Callback<Employee>(x=> invokedCorrectly = true);
//Act
FireEmployee(dataStore.Object, robert);
//Assert
Assert.IsTrue(invokedCorrectly);
This is expected behavior in moq as arguments captured by invocation are compared by identity, using Equals not by value. Once you changed captured arguments you actually directly change the invocation. Then later on when you verify these objects are not the same anymore. As #Old Fox alredy provided one solution I will just add one more. You could use Verify() instead of VerifyAll() the difference is that the first one will only check setups marked as Verifiable(). In your case something like this:
[Test]
public void TestViaSetupVerifyAll()
{
//Arrange
Mock<IDataStore> dataStore = new Mock<IDataStore>();
dataStore
.Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)))
.Verifiable();
var robert = new Employee { IsEmployed = true };
//Act
FireEmployee(dataStore.Object, robert);
//Assert
dataStore.Verify();
}
If you mark setup as Verifiable() you could be able to capture the particular invocation with the state of the object you actually expect.
I am writing my own tests to mock entity framework on .NET core, however I have been having some trouble as some LINQ queries work fine and others throw a not implemented exception. This is the basic query guide I have been using:
https://learn.microsoft.com/en-us/ef/core/querying/basic
Here is an example of the problem with two tests. The first uses the ToList() query to load all data, and tests against the first and only entity. This works but is not ideal. The second test attempts to grab the specific entity using its Id, following the guide's 'Loading a single entity' instructions. However, this one throws the exception.
[TestMethod]
public void Test_AddBook()
{
// Arrange
var context = new TestContext();
var service = new BookStore(context);
// Act
service.AddBook(1, "Wuthering Heights", "Emily Brontë", "Classics", 7.99, 5);
//Assert
var books = context.Books.ToList();
Assert.AreEqual(books[0].Title, "Wuthering Heights");
}
[TestMethod]
public void Test_AddBook2()
{
// Arrange
var context = new TestContext();
var service = new BookStore(context);
// Act
service.AddBook(1, "Wuthering Heights", "Emily Brontë", "Classics", 7.99, 5);
//Assert
var book = context.Books.Single(b => b.Id == 1);
Assert.AreEqual(book.Title, "Wuthering Heights");
}
The debugger shows the id of the book in the context to be 1:
And the code for TestContext:
class TestContext : IBookContext
{
public TestContext()
{
this.Books = new TestDbSet<Book>();
}
public DbSet<Book> Books { get; set; }
public int SaveChangesCount { get; private set; }
public int SaveChanges()
{
this.SaveChangesCount++;
return 1;
}
}
This is the tutorial I have been following, with a few things changed to adapt it to .NET Core:
https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/writing-test-doubles
We are using Machine.Specification as our test framework on my current project. This works well for most of what we are testing. However, we have a number of view models where we have 'formatted' properties that take some raw data, apply some logic, and return a formatted version of that data.
Since there is logic involved in the formatting (null checks, special cases for zero, etc.) I want test a number of possible data values including boundary conditions. To me, this doesn't feel like the right use case for MSpec and that we should drop down into something like NUnit where I can write a data-driven test using something like the [TestCase] attribute.
Is there a clean, simple way to write this kind of test in MSpec, or am I right in my feeling that we should be using a different tool for this kind of test?
View Model
public class DwellingInformation
{
public DateTime? PurchaseDate { get; set; }
public string PurchaseDateFormatted
{
if(PurchaseDate == null)
return "N/A";
return PurchaseDate.Value.ToShortDateString();
}
public int? ReplacementCost { get; set; }
public string ReplacementCostFormatted
{
if(ReplacementCost == null)
return "N/A";
if(ReplacementCost == 0)
return "Not Set";
return ReplacementCost.ToString("C0");
}
// ... and so on...
}
MSpec Tests
public class When_ReplacementCost_is_null
{
private static DwellingInformation information;
Establish context = () =>
{
information = new DwellingInformation { ReplacementCost = null };
};
It ReplacementCostFormatted_should_be_Not_Available = () => information.ReplacementCostFormatted.ShouldEqual("N/A");
}
public class When_ReplacementCost_is_zero
{
private static DwellingInformation information;
Establish context = () =>
{
information = new DwellingInformation { ReplacementCost = "0" };
};
It ReplacementCostFormatted_should_be_Not_Set = () => information.ReplacementCostFormatted.ShouldEqual("Not Set");
}
public class When_ReplacementCost_is_a_non_zero_value
{
private static DwellingInformation information;
Establish context = () =>
{
information = new DwellingInformation { ReplacementCost = 200000 };
};
It ReplacementCostFormatted_should_be_formatted_as_currency = () => information.ReplacementCostFormatted.ShouldEqual("$200,000");
}
NUnit w/TestCase
[TestCase(null, "N/A")]
[TestCase(0, "Not Set")]
[TestCase(200000, "$200,000")]
public void ReplacementCostFormatted_Correctly_Formats_Values(int? inputVal, string expectedVal)
{
var information = new DwellingInformation { ReplacementCost = inputVal };
information.ReplacementCostFormatted.ShouldEqual(expectedVal);
}
Is there a better way to write the MSpec tests that I'm missing because I'm just not familiar enough with MSpec yet, or is MSpec really just the wrong tool for the job in this case?
NOTE: Another Dev on the team feels we should write all of our tests in MSpec because he doesn't want to introduce multiple testing frameworks into the project. While I understand his point, I want to make sure we are using the right tool for the right job, so if MSpec is not the right tool, I'm looking for points I can use to argue the case for introducing another framework.
Short answer, use NUnit or xunit. Combinatorial testing is not the sweet spot of mspec and likely will never be. I never cared for multiple test frameworks in my projects, especially when a second tool works better for specific scenarios. Mspec works best for behavioral specifications. Testing input variants is not.