FREELessons: 22Length: 5.5 hours

Next lesson playing in 5 seconds

  • Overview
  • Transcript

2.3 Adding Functionality by Testing

Now we're ready to start adding functionality to our post controller. We'll be good programmers and write some tests, and I'll show you some testing tools you can use in your own applications.

2.3 Adding Functionality by Testing

Now we're ready to start writing functionality for our post controller. And we could just start writing that code, but we should also be good programmers and test our code, because testing is important regardless of what project that we're working on. So we're going to get some use out of the test project we created when we first created the NVCCMS project and right now there's just a single file called unitest1.cs. Now we're probably never going to use this file so let's just go ahead and delete that because I like to have some order inside of my test projects. Since we're going to be testing the post controller, which is part of our admin, then let's add a new folder called admin. And we will stick all of our tests regarding the admin portion of our application inside of this admin folder. Let's add another folder inside of admin called controllers. And as you can expect, this is going to contain all of the tests for our controllers. So finally we want to add a new unit test. Now if we choose unit test right here from the menu, it's going to create a unittest1.cs file. It doesn't give us a chance to name the file. So if we wanted to instead, we could go to Add and then New Item, and then we can do a search for test, and here we see basic unit tests. So lets click on the name here, lets call this as post controller tests and then we will add it to our project. Now testing our post controller is going to force us to make some decisions on how we access our data. For example, the first test that we will write is the edit method that handles get requests. This receives an ID and we are supposed to retrieve the appropriate post from the data store with the given ID. Now, the most common pattern that's used is the repository pattern. So we will just go ahead and use that. Let's go to our solution explore. And inside of our main project, let's add a new folder called Data. Now this is a personal preference of mine. I like to put everything that interfaces with the data store inside of the data namespace. And then, if I need to, I break it down even more from there. If I have a SQL data store, then I might have a SQL namespace. If I'm using the filesystem, then I'll have filesystem and so on and so forth. But in this case, we don't need to worry about that. We need a interface for a post repository. So let's add a new item. Let's choose interface and we want to call this I Post Repository. And we need to make this repository public, so public interface I repository. And our post controller needs a reference to a repository because it needs some way to access our data. It's going to do so with a repository, so we need to supply the repository to our controller. So let's go to our post controller and let's add a constructor that accepts an I post repository. So public post controller and I post repository. We need a using statement for our data name space, and then let's just call this repository. And then we should also have a private field. Let's make it read-only, because we don't need to change it after we assign it. And we'll call it _repository. And then inside of the constructor, we will set _repository equal to our repository parameter. Now if you're familiar with testing you've probably already figured out that I don't follow the rules of test driven development. I don't do red phases or green phases. I don't write code that specifically fails or passes. I just write code and then I write tests. Unless if don't know how I want to approach something, in which case I write tests and then I write the code to fulfill those tests. It's just a lot easier that way. So I'm going to write code first for the edits method, and then I'm going to write tests for that code. So the edit method is supposed to retrieve a post and then pass that post on to the view. So, let's create a variable called post and we will use our repository. And we don't have this method yet but we will call get and pass in an ID. And then we will take that post and pass it on to the view. Now it's possible that post is null, because we could be given an ID for a post that does not exist. In which case, we should still return an ActionResult, but it will be a not found result, because if the ID in the URL doesn't exist, then that resource doesn't exist. So if post equals to null, then we will simply return an http/notFound. And that's basically all that we need to do here. So let's implement this get method, actually, we're not implementing. Let's stub out this get method on our interface. So you can put your cursor over get and hit Ctrl + dot, and then enter. And then we can go to our repository. And instead of returning an object, we want to return a post, but we need a using statement for our model's name space. And then we can go to our test and then we can write the test for this method. So let's, first of all, change this name. Let's call it Edit_GetRequest Sends post to view. And the first thing we need is our controllers. So let's new up our post controller, and we need a using statement for our controller's name space. But we need a repository to pass to our controller. And we can't create an object from an interface. So we're going to have to mock one, and to do that we're going to use a library called JustMock Lite. It is made by a company called Teleric, and it is a wonderful tool. It's also completely free. So if we go to NuGet, well, first of all, you want to right-click on References in your test project, Manage Nuget Packages, and then we can just do a search for JustMock. There's only one item here, at least there is right now, Telerik JustMock Light. We want to install that and accept the license, and that's it. So, now we need to create a repository object, so that we can pass it to our controller. So let's do var, let's call it repo. We want to use a class called mock, which is in Telerik.JustMock. This has a method called create that we can specify that we want an IPostRepository and we need a using statement for our data name space. And then this is going to give us a repository object that we can pass to our controller. Now there is no behavior for this repository. We're going to have to define that behavior, but we'll do that in a minute. So in order to check to see if we have an actual post that's being sent to the view, we need to first of all call the edit method and get the result of calling that action method. So let's create a variable called result, and let's cast this as view result. And we want to call controller.Edit, and we want to pass in an ID. Let's create a variable that's going to have this. Let's call it ID, and we'll just call it test-post. And then we will use that as arguments for the edit method. Now we need a using statement here for the view results so control dot and we don't have anything for name spaces. So we need to probably install the MVC framework for our test project. So let's right-click on references once again. We want to manage NuGet Packages. And let's do a search for Microsoft, and we should see MVC somewhere here. Yes, here it is, right at the top. So we will install this. This also has some dependencies. And then we will close. And now we can add a using statement for system.Web.Mvc. And so now that we have our result, we want to get the model. So let's call this model, and we need to cast this as a Post, and we need another using statement for our model's namespace, and we will just do result.Model. And then we can compare the id of our model with what we expect the id to be, which is our id variable. So we can assert AreEqual and then we have the expected value, which is id. And then we have the actual value, which is model.Id. Now this is going to fail because we haven't specified any behavior for our repository. So if we go to our Test Explorer and then run all of the tests, we see that it failed. So we need to define the behavior of our repository. If we call the get method and pass in this value for the id, then we should get a post object that has that as the id. We don't have to worry about any of the other properties, because all we are checking for is the id. So we want to use this Mock class once again, there's a method called Arrange. And we are going to arrange the behavior of our repository. So whenever we call our repository's Get method and we pass in the id, then we want it to return a new Post that has an Id equal to id. And now if we run this test again, then it should pass. Or it could fail. So let's click No. Let's see, Telerik.JustMock.Expectations does not contain a definition for return. That is my bad. It's Returns. So we can run this again, and now the test should pass, and it does. So the functionality for the GetRequest sending a post to the view works. Now we need to test to ensure that when a post doesn't exist that we get a not found result. So let's change the name of this to NotFoundResult, and here we're testing the result, so we no longer need the model, and we want to return null whenever we call repo.Get(id). So we need to change this return value, so let's just pass in null and, well, that's not going to work. Let's see, there's some ambiguity here. So let's see if we can cast this as Post. And that works, that's great. And for the result we don't even need to cast it as a view result because we just need the action result. And instead of using our equal we can do Assert.IsTrue, and we can say that result is HttpNotFoundResult. And then we can go to our Test Explorer, then we can run all of our tests again and we will hopefully see that they both pass, and they do. Now we need to focus on the other edit method, the one that handles post requests. So we're going to write the code for this form and the first thing that we need to address is the parameter. Here we are accepting a post object. This contains all the information that's coming from the form. But we need to know more than that, because if we look at our view, we added the ability to modify the Id. So when the user submits the form we need two things. We need the current value of the Id, that is, the Id as it is stored within our data store, and then we need the new value of the Id. Now they could be the same, but we need to know what post that we are modifying. So that we can modify it with the data from our form. And we already have that value. It's part of the URL. So we need another parameter here. We need a string id. But we can't use id, because we have a form field called id. So that is part of the HTTP request, and the model binder is going to take that value and bind it to the second parameter. So we need to change our variable inside of our route here. So we can just call this postId, and then we can change the parameter to postId. And for the sake of consistency, we should do that on the other Edit method. So let's change those values, and everything is nice and consistent now. So the first thing we need to do is check to see if we have a post with the given postId. So we want var post = and we want to use our _repository object. We will call the Get method and pass in postId. Now it's possible that the post doesn't exist. So if it doesn't, then we need to return a NotFound result, so HttpNotFound. And then we can check the model state to see if it's valid. If not, then we send the model back to the View. But then we need to do the actual editing. So we want to use our repository once again. And we don't have this method yet, but we can specify the postId that we want to modify, and then we can pass in our post object. And then the repository is responsible for making the necessary edits. So we can hit Ctrl+., and then Enter, and that is going to add that method to our ipost repository interface, and we can change these parameter names. Let's call this id and then let's call this item. Or rather, let's make this a little more specific, updatedItem. And now that we have some code, we can write some tests. And our first test is going to ensure that if a post doesn't exist, that it returns an HttpNotFound result. Now we have something very similar, because we just wrote a test method for the edit method that handles get requests. So we can copy and paste that and use it as a basis. So we need to change the name to Edit_PostRequestNotFoundResult. We also need to change how we call the edit method. We need to pass in a new Post object, although it doesn't have to be an actual Post object. We could pass null here, and that would be fine. And then the id, although, you know, I don't like that ordering. I would prefer to have id first and then the Post object. So let's modify our API so that that is the case. So let's change our parameter list on the method itself. And then let's go back to our tests. And I really think that that's all that we need to do. So let's go to our Test Explorer, let's Run All, and everything should pass. And it indeed does. So the next thing we need to test is to ensure that if the model state is not valid that we return the model back to the view. So this is going to be somewhat similar to the very first test that we wrote, except that we aren't going to compare IDs or anything, we just need to ensure that a model was passed back to the view. So let's change the name to Edit_PostRequestSendsPostToView and the way that we make this work is by adding an error to our model state. We do that with our controller object. We have a property called ViewData and then ModelState, and then finally there's an AddModelError method. It really doesn't matter what we pass here. So we can pass anything for the key and then anything for the error message. And that's going to be enough to invalidate our model. Next we need to call the Edit method passing in the id. We also need to pass in a new Post object. And let's go ahead and let's populate something here. Let's go ahead and set the ID. Let's set this to test-post- To and then we can compare ID's, so that we can take this string value, use it as the expected value in the R equal method and then model ID. So we are essentially doing the same thing except that we added an error to our model state. We called it the edit method and got the view result, we got the model from that view result, and then we are ensuring that the model that was passed to the view is this post object. So let's go back to our test explorer, let's run all, and hopefully all four of our tests should pass. And they do. Finally, we need to ensure that our edit method calls the repository's edits method passing in a string and a post object, and that it returns a RedirectToAction result. So this test is going to be a little bit different than the other tests that we have written, but it is still going to be relatively simple. So we can take one of our previous tests and and copy it and paste it because there are still a lot of things that we will need. Now lets change this to Edit_PostRequestCallsEditAndRedirects() And we don't need an id here. So we can get rid of that. We still need our repository, we still need our controller. We don't need this ModelState error, so we can get rid of that. And we don't really need to cast this as a ViewResult. Now we need a valid string here, so let's just pass the string of foo. We don't need the model, because we're not going to test that. And our assertion here is going to be different. So the first thing we need to change from the code that we have now is our arrangement. We still need to arrange our repository, but instead of arranging the Get method, we want to arrange the Edits method. And we want to say that Edit method has to be called. So we once again use our mock class, we use the Arrange method. And here we have repo.edit. Now it doesn't matter what we pass to the edit method. So we have a special thing that we can do. We can use this arg and then is any, and we specify string as the data type so we can pass any string. We can also pass any post. And then we can specify that this must be called. Although it helps if you have the right syntax, so instead of a question mark, we need an angle bracket, and then we must be called. So this is going to ensure that we have to call the Edits method. We call the controllers Edits method, and get its result. But then we need to assert that the repositories edit method was called. We use our mock class once again. And we have an Assert, and we just pass in repo. This is going to assert that our repository's Edit method was called. And then we want another assertion that is true. We want to ensure that our result is a redirect to route result. And then we will just run all of our tests, and hopefully this new one will pass. First of all, let's hope that it builds. And it does, so let's look. And all of our tests pass. So we wrote all of the tests that we need for the two Edits methods. And in a real world project we would write the tests for the Create methods as well as the Index methods. But in order to save time, we're just going to write this code. So for the Get version of the Create method we don't really need to do anything. We're just creating a post object and passing that to the view. Because that's all that we need to do there, that's just displaying the form for creating a new post. But for the Create method that handles post requests we first of all need to ensure that the model state is valid, if it's not then we go back to the view passing in the model and then we need to create the post in our data store. So we will use our repository, and we can use a variety of terminology here. We can use Create, we can use Add, we can use Insert. Let's go with Create because we have Get, we have Edit. Creates kind of makes sense here, and we will pass in our model. That should create a new record, or new file, depending upon what our data store is for our given model, and then we redirect to action. So let's hit control dot to add the Create method to our repository. Let's ensure that everything is fine. We might want to change the return type here, but we can do that if and when we need to do that. And then, finally, for the index method, this is going to display all of the posts that we have in our data store. So we need some way of retrieving all of our posts. So, once again, we will use our repository. And we are going to need another method we can call it GetAll. Let's go ahead and hit control dot and enter to add that method to our interface and then we will pass post there, although this needs to be posts because we should hopefully have more than one post. So we will pass posts there. Let's go to our I post repository instead of an object let's make this an IEnumerable(Post) and that should be it for now. So now we have a post controller that is almost complete. We don't have everything that we need, but we can come back after we focus on some other things. Like, for example, categories. We need some way to manage the categories that we are going to apply to our posts. So we will get started with that in the next lesson.

Back to the top