Reputation: 249
I'm attempting to set up a unit test with MSTest and Moq for a live system that posts json from a form to a database. The system itself works just fine, but I've been tasked with attempting to build some tests for it. The ajax call in the view I'm working with goes to following HttpPost method one of the controllers:
[HttpPost]
public ActionResult Add(Request model)
{
return ProcessRequest(model, UserAction.Create);
}
Which leads to the WebAPI controller:
public int Post([FromBody]Request value)
{
try
{
var id = myRepository.AddRequest(value);
foreach (var day in value.Days)
{
day.RequestId = id;
myRepository.AddRequestDay(day);
}
return id;
}
catch
{
return -1;
}
}
With my test, I thought that using TransactionScope would be a good idea so I'm not actually saving any data on the database. If there is a better approach, please enlighten me:
[TestMethod]
public void API_Request_Post()
{
using (TransactionScope ts = new TransactionScope())
{
var jsonObject = //some json scraped from a test post
var request = new Mock<HttpRequestBase>();
//This is where I'm stuck. I can't find anything in Setup that lets me prep the Post body for when the controller gets to it.
//request.Setup(x => x.InputStream).Returns(jsonObject);
RequestController controller = new RequestController();
//This is another point that I don't understand. I make the call for post happy with a reference to the model instead of the actual json?
var result = controller.Post(new Models.Request() );
Assert.IsTrue(result > -1);
}
}
Any help trying to determine what part of the HttpRequest I need to give my json to would be greatly appreciated (and helping me understand the Post would just be icing on the cake).
Upvotes: 0
Views: 4091
Reputation: 1269
I hope I'm not telling you something you already know, but it looks like you are maybe questioning where to start? That is the hardest part of testing...
Just to make sure we are on the same page, the key to knowing what to test is describing the scenario, and the key to unit testing that scenario is Isolation.
What this means is you want to isolate in regard to the class "under test".
Also, code is much easier to test if you write the test first and then the code to make that pass. You're not in that situation, so that means that the code you have MAY NOT be testable without changing it.
And finally, given any external / 3rd party system, unless you are doing an "exploratory test" then you do not want to test 3rd party stuff, namely, http posting / getting. Instead, you want to test your code and how it performs.
Assuming you knew that or that all makes sense, then, this part will be be obvious too.
Moq, or any other mocking framework, is designed to stand in for objects / services that your class under test collaborates with, in order to aid in isolation. Given two classes, ClassA and ClassB, where ClassA acts on ClassB, you want to fake/mock Class B when giving it to ClassA so you can assert / verify that ClassA behaves as you would expect for the given scenario. This seems naive at first, but consider that you will also do the same for ClassB, you then have a suite of tests that isolate in respect to what they are testing, and that gives you great coverage.
And the key to isolation is injection, make sure that if ClassA acts on ClassB, you pass ClassB in to ClassA's constructor, that way you can give it a fake class B. Class B shouldn't do anything, other than respond how you say it should respond so you can prove that ClassA behaves appropriately in that situation.
Some people frown on changing code to make it testable, but my argument is if you wrote the code to be testable in the first place, you wouldn't have to change it, so try to refactor but not reengineer.
So, what that means is you will want a few different scenarios, each with each tests that isolate in respect to what you care about.
The key to good tests is figuring out what it is you want to test, and then arranging your tests so that it is clear what you are doing.
Test Class Name DOES NOT need to have "Test" in it; that's redundant. Explain what the scenario is instead; who is involved, etc.
The Test Method should say what the action is that you care about testing; what states are you in, etc.
** Inside the method** for now follow the "Arrange, Act, Assert" (aka Given, When, Then) approach:
Arrange: set up all of your mocks or any variables you need here, including the one class you are testing, such as your real Controller but fake myRepository and fake value
Act: do the actual action, such as Post()
Assert: Prove that the behaviors you expected happened, such as when you give it a value
with four days, then you expect that:
As I'm not entirely sure what the intent of the test is there, and I don't know all of the code, I'm going to give an example that I think will relate well and will hopefully also show how to set up the mocks (and ideally why you would!)
Also if this were truly a unit test, you would usually strive for one test per assertion / verification so you do not have to debug the test, only the failing code, but I'm putting three in here for "simplicity".
In this test, you'll see that I:
POST
, on the live controller with mocked collaborators (repository and request),And then I verify that POST
performs / behaves as expected.
[TestClass]
public class GivenAValidRequestAndRepository(){
[TestMethod]
public void WhenWeReceiveAPostRequest(){
//Arrange / Given
var repository = new Mock<IRepository>();
var request = new Mock<IRequest>();
request.Setup ( rq => rq.ToString() )
.Returns ( "This is valid json ;-)" );
request.Setup ( rq => rq.Days )
.Returns ( new List<IDay> {
"Monday",
"Tuesday",
} );
var controller = new RequestController( repository.Object );
//Act / When
int actual = controller.Post( request.Object );
//Assert / Verify
// - then we add the request to the repository
repository.Verify(
repo => repo.AddRequest( request, Times.Once() );
// - then we add the two days (from above setup) in the request to the repository
repository.Verify(
repo => repo.AddRequestDays( It.IsAny<IDay>(), Times.Exactly( 2 ));
// - then we receive a count indicating we successfully processed the request
Assert.NotEqual( -1, actual );
}
}
Your goal should not be to make your boss happy that you have written a test. Instead, strive for valuable and expressive tests that you will be able to maintain down the road. You won't get it perfect (nor should you try) just make sure that what you are testing adds value. Cover things that if they were to change in code, your test would then fail, indicating you have a bug.
I hope this helps, please reply with comments / questions.
Upvotes: 3