Download presentation
Presentation is loading. Please wait.
Published bySherman Owens Modified over 8 years ago
1
Unit testing of the Services Telerik Software Academy http://academy.telerik.com Web Services and Cloud
2
Ways of web service testing Unit Testing Integration Testing Complete Testing of Web Services Unit testing the data layer Unit testing the repositories layer Unit testing the controllers Integration testing the web services
4
Web service unit testing is much like regular unit testing Writing test methods to test methods etc.. Yet a service is build from many more components than POCO objects There are the objects, service routing, media types, HTTP Status codes, etc…
5
When a web service is ready for test, the testing itself is performed in the following steps: Write Unit Tests to test the C# objects Test all objects, their constructors, their methods Write the Integration Testing Test the application as if a user tests it
6
Testing the Work of the Controllers
7
The core idea of Unit testing is to test small components of an application Test a single behavior (a method, property, constructor, etc…) Unit tests cover all C# components of the app Models and data layer Like repositories and DB/XML read/write Business layer Controllers and their actions
9
The data layer is the one thing that most of the time does not need testing The idea of the data layer is to reference a data store with a ready-to-use framework EntityFramework, NHibernate, OpenAccess They are already tested enough No point of testing dbContext.Set.Add(), right?
11
It is essential to test the implementations of our repositories The repositories contain the data store logic All custom (developer) logic must be tested A missing dbContext.SaveChanges() can cause a lot of pain
12
How to test the data store logic? Writing and deleting the original (production) database is not quite a good option Imagine a failed test that leaves 100k test records in the database
13
A few ways exist to unit test a data store Manually create a copy of the data store and work on the copy Backup the original data store, work on the original, and restore the backup when the tests are over Use transactions, to prevent commit to the data store
14
When testing with transactions, the changes done are not really applied to the data store Whatever commited, if tran.Complete() is not called, the transaction logic is rolled back How to use transactions in unit tests? Create a static TransactionScope instance Initialize the transaction in TestInitialize() Dispose the transaction in TestCleanup()
15
Live Demo
16
What parts of the repositories should our tests cover? Test for correct behavior Add, Delete, Get, All, etc… Test for incorrect behavior Test Add with Category that has NULL name
17
Live Demo
19
Testing the services layers actually means testing the controllers and the REST API Test if controllers work correctly as C# objects Using mocking or fake repositories Test if the endpoints of the REST services work correctly Check the StatusCode and Content
20
The Unit testing of the controllers is not much of a big deal Test them as regular C# classes Instantiate an object, and test its methods (actions) The repositories can be mocked/faked for easier testing If not mocked, the transaction technique should be used again
21
To test the controllers, repositories should be faked i.e. create a in-memory repository that implements the IRepository interface class FakeRepository : IRepository where T:class { IList entities = new List (); IList entities = new List (); public T Add(T entity) { public T Add(T entity) { this.entities.Add(entity); this.entities.Add(entity); return entity; return entity; } public T Get(int id) public T Get(int id) { return this.entities[id]; return this.entities[id]; } …}
22
With the Fake Repository present, controllers can be tested by passing their constructor a fake rep public void GetAll_OneCategoryInRepository_ReturnOneCategory() { //arrange var repository = new FakeRepository (); var repository = new FakeRepository (); var categoryToAdd = new Category(){ Name = "Test category" }; var categoryToAdd = new Category(){ Name = "Test category" }; repository.Add(categoryToAdd); repository.Add(categoryToAdd); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoriesModels = controller.GetAll(); var categoriesModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoriesModels.Count() == 1); Assert.IsTrue(categoriesModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoriesModels.First().Name); categoriesModels.First().Name);}
23
With the Fake Repository present, controllers can be tested by passing their constructor a fake rep public void GetAll_OneCategoryInRepository_ReturnOneCategory() { //arrange var repository = new FakeRepository (); var repository = new FakeRepository (); var categoryToAdd = new Category(){ Name = "Test category" }; var categoryToAdd = new Category(){ Name = "Test category" }; repository.Add(categoryToAdd); repository.Add(categoryToAdd); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoriesModels = controller.GetAll(); var categoriesModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoriesModels.Count() == 1); Assert.IsTrue(categoriesModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoriesModels.First().Name); categoriesModels.First().Name);} Prepare the repository
24
With the Fake Repository present, controllers can be tested by passing their constructor a fake rep public void GetAll_OneCategoryInRepository_ReturnOneCategory() { //arrange var repository = new FakeRepository (); var repository = new FakeRepository (); var categoryToAdd = new Category(){ Name = "Test category" }; var categoryToAdd = new Category(){ Name = "Test category" }; repository.Add(categoryToAdd); repository.Add(categoryToAdd); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoriesModels = controller.GetAll(); var categoriesModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoriesModels.Count() == 1); Assert.IsTrue(categoriesModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoriesModels.First().Name); categoriesModels.First().Name);} Prepare the repository Pass it to the controller
25
With the Fake Repository present, controllers can be tested by passing their constructor a fake rep public void GetAll_OneCategoryInRepository_ReturnOneCategory() { //arrange var repository = new FakeRepository (); var repository = new FakeRepository (); var categoryToAdd = new Category(){ Name = "Test category" }; var categoryToAdd = new Category(){ Name = "Test category" }; repository.Add(categoryToAdd); repository.Add(categoryToAdd); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoriesModels = controller.GetAll(); var categoriesModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoriesModels.Count() == 1); Assert.IsTrue(categoriesModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoriesModels.First().Name); categoriesModels.First().Name);} Prepare the repository Pass it to the controller Act on the controller
26
With the Fake Repository present, controllers can be tested by passing their constructor a fake rep public void GetAll_OneCategoryInRepository_ReturnOneCategory() { //arrange var repository = new FakeRepository (); var repository = new FakeRepository (); var categoryToAdd = new Category(){ Name = "Test category" }; var categoryToAdd = new Category(){ Name = "Test category" }; repository.Add(categoryToAdd); repository.Add(categoryToAdd); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoriesModels = controller.GetAll(); var categoriesModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoriesModels.Count() == 1); Assert.IsTrue(categoriesModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoriesModels.First().Name); categoriesModels.First().Name);} Prepare the repository Pass it to the controller Act on the controller Assert the result
27
Live Demo
28
Creating fake repository for each and every unit test is kind of boring Here comes the mocking Provide objects that mimic some functionality JustMock/Moq provide mocking functionality Creates a fake instance of an interface and implement only the functionality needed
29
[TestMethod] public void GetAll_SingleCategoryInRepository_ReturnsTheCategory() { //arrange //arrange var repository = Mock.Create >(); var repository = Mock.Create >(); var categoryToAdd = GetTestCategory(); var categoryToAdd = GetTestCategory(); IList entities = new List (){ categoryToAdd }; IList entities = new List (){ categoryToAdd }; Mock.Arrange(() => repository.All()) Mock.Arrange(() => repository.All()).Returns(() => entities.AsQueryable()); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoryModels = controller.GetAll(); var categoryModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoryModels.Count() == 1); Assert.IsTrue(categoryModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoryModels.First().Name); categoryModels.First().Name);}
30
[TestMethod] public void GetAll_SingleCategoryInRepository_ReturnsTheCategory() { //arrange //arrange var repository = Mock.Create >(); var repository = Mock.Create >(); var categoryToAdd = GetTestCategory(); var categoryToAdd = GetTestCategory(); IList entities = new List (){ categoryToAdd }; IList entities = new List (){ categoryToAdd }; Mock.Arrange(() => repository.All()) Mock.Arrange(() => repository.All()).Returns(() => entities.AsQueryable()); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoryModels = controller.GetAll(); var categoryModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoryModels.Count() == 1); Assert.IsTrue(categoryModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoryModels.First().Name); categoryModels.First().Name);} Create the mock object
31
[TestMethod] public void GetAll_SingleCategoryInRepository_ReturnsTheCategory() { //arrange //arrange var repository = Mock.Create >(); var repository = Mock.Create >(); var categoryToAdd = GetTestCategory(); var categoryToAdd = GetTestCategory(); IList entities = new List (){ categoryToAdd }; IList entities = new List (){ categoryToAdd }; Mock.Arrange(() => repository.All()) Mock.Arrange(() => repository.All()).Returns(() => entities.AsQueryable()); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoryModels = controller.GetAll(); var categoryModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoryModels.Count() == 1); Assert.IsTrue(categoryModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoryModels.First().Name); categoryModels.First().Name);} Create the mock object Mock the All() behavior
32
[TestMethod] public void GetAll_SingleCategoryInRepository_ReturnsTheCategory() { //arrange //arrange var repository = Mock.Create >(); var repository = Mock.Create >(); var categoryToAdd = GetTestCategory(); var categoryToAdd = GetTestCategory(); IList entities = new List (){ categoryToAdd }; IList entities = new List (){ categoryToAdd }; Mock.Arrange(() => repository.All()) Mock.Arrange(() => repository.All()).Returns(() => entities.AsQueryable()); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoryModels = controller.GetAll(); var categoryModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoryModels.Count() == 1); Assert.IsTrue(categoryModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoryModels.First().Name); categoryModels.First().Name);} Create the mock object Mock the All() behavior Act on the controller
33
[TestMethod] public void GetAll_SingleCategoryInRepository_ReturnsTheCategory() { //arrange //arrange var repository = Mock.Create >(); var repository = Mock.Create >(); var categoryToAdd = GetTestCategory(); var categoryToAdd = GetTestCategory(); IList entities = new List (){ categoryToAdd }; IList entities = new List (){ categoryToAdd }; Mock.Arrange(() => repository.All()) Mock.Arrange(() => repository.All()).Returns(() => entities.AsQueryable()); var controller = new CategoriesController(repository); var controller = new CategoriesController(repository); //act //act var categoryModels = controller.GetAll(); var categoryModels = controller.GetAll(); //assert //assert Assert.IsTrue(categoryModels.Count() == 1); Assert.IsTrue(categoryModels.Count() == 1); Assert.AreEqual(categoryToAdd.Name, Assert.AreEqual(categoryToAdd.Name, categoryModels.First().Name); categoryModels.First().Name);} Create the mock object Mock the All() behavior Act on the controller Assert the result
34
Live Demo
35
GET actions are easy to test They return POCO objects How to test POST actions? They return HttpResponseMessage Unfortunately POST actions require additional configuration due to the Request object they use
36
A simple POST action: public HttpResponseMessage Post(CategoryModel model) { var entity = this.categoriesRepository.Add(model); var entity = this.categoriesRepository.Add(model); var response = Request.CreateResponse ( var response = Request.CreateResponse ( HttpStatusCode.Created, HttpStatusCode.Created, entity); entity); var resourceLink = Url.Link("DefaultApi", var resourceLink = Url.Link("DefaultApi", new { id = entity.Id }); new { id = entity.Id }); response.Headers.Location = new Uri(resourceLink); response.Headers.Location = new Uri(resourceLink); return response; return response;}
37
A simple POST action: public HttpResponseMessage Post(CategoryModel model) { var entity = this.categoriesRepository.Add(model); var entity = this.categoriesRepository.Add(model); var response = Request.CreateResponse ( var response = Request.CreateResponse ( HttpStatusCode.Created, HttpStatusCode.Created, entity); entity); var resourceLink = Url.Link("DefaultApi", var resourceLink = Url.Link("DefaultApi", new { id = entity.Id }); new { id = entity.Id }); response.Headers.Location = new Uri(resourceLink); response.Headers.Location = new Uri(resourceLink); return response; return response;} Run in unit test, Request has a value of null If a controller is invoked outside of WebAPI environment, the Request object is not set
38
To have a non-null value of the Request object, it must be set up manually private void SetupController(ApiController controller) { var request = new HttpRequestMessage() var request = new HttpRequestMessage() { RequestUri = new Uri("http://test-url.com")}; { RequestUri = new Uri("http://test-url.com")}; controller.Request = request; controller.Request = request; var config = new HttpConfiguration(); var config = new HttpConfiguration(); config.Routes.MapHttpRoute( config.Routes.MapHttpRoute( name: "DefaultApi", name: "DefaultApi", routeTemplate: "api/{controller}/{id}", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); defaults: new { id = RouteParameter.Optional }); controller.Configuration = config; controller.Configuration = config; controller.RequestContext.RouteData = new HttpRouteData( controller.RequestContext.RouteData = new HttpRouteData( route: new HttpRoute(), route: new HttpRoute(), values: new HttpRouteValueDictionary { values: new HttpRouteValueDictionary { { "controller", "categories" } { "controller", "categories" } }); });}
39
To have a non-null value of the Request object, it must be set up manually private void SetupController(ApiController controller) { var request = new HttpRequestMessage() var request = new HttpRequestMessage() { RequestUri = new Uri("http://test-url.com")}; { RequestUri = new Uri("http://test-url.com")}; controller.Request = request; controller.Request = request; var config = new HttpConfiguration(); var config = new HttpConfiguration(); config.Routes.MapHttpRoute( config.Routes.MapHttpRoute( name: "DefaultApi", name: "DefaultApi", routeTemplate: "api/{controller}/{id}", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); defaults: new { id = RouteParameter.Optional }); controller.Configuration = config; controller.Configuration = config; controller.RequestContext.RouteData = new HttpRouteData( controller.RequestContext.RouteData = new HttpRouteData( route: new HttpRoute(), route: new HttpRoute(), values: new HttpRouteValueDictionary { values: new HttpRouteValueDictionary { { "controller", "categories" } { "controller", "categories" } }); });} Create a Request object
40
To have a non-null value of the Request object, it must be set up manually private void SetupController(ApiController controller) { var request = new HttpRequestMessage() var request = new HttpRequestMessage() { RequestUri = new Uri("http://test-url.com")}; { RequestUri = new Uri("http://test-url.com")}; controller.Request = request; controller.Request = request; var config = new HttpConfiguration(); var config = new HttpConfiguration(); config.Routes.MapHttpRoute( config.Routes.MapHttpRoute( name: "DefaultApi", name: "DefaultApi", routeTemplate: "api/{controller}/{id}", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); defaults: new { id = RouteParameter.Optional }); controller.Configuration = config; controller.Configuration = config; controller.RequestContext.RouteData = new HttpRouteData( controller.RequestContext.RouteData = new HttpRouteData( route: new HttpRoute(), route: new HttpRoute(), values: new HttpRouteValueDictionary { values: new HttpRouteValueDictionary { { "controller", "categories" } { "controller", "categories" } }); });} Create a config Create a Request object
41
Live Demo
43
Integration testing aims to cover the work of the whole application Not small components like unit testing Integration tests should work like a user Test what a user sees in combination of all application components mixed together
44
When integration testing WebAPI, controllers and their actions are assumed to be working correctly In WebAPI, integration tests should cover: The endpoints of the RESTful services Test if the endpoint reaches the correct action Test the serialization of the data Does it work with JSON/XML Is the data serialized correctly
45
Integration testing a GET request: [TestMethod] public void GetAll_SingleCategory_StatusCodeOkAndNotNullContent() { var mockRepository = Mock.Create >(); var mockRepository = Mock.Create >(); var models = new List (); var models = new List (); models.Add(new Category() { Name = "Test Cat" }); models.Add(new Category() { Name = "Test Cat" }); Mock.Arrange(() => mockRepository.All()) Mock.Arrange(() => mockRepository.All()).Returns(() => models.AsQueryable());.Returns(() => models.AsQueryable()); var server = new InMemoryHttpServer ( var server = new InMemoryHttpServer ( "http://localhost/", "http://localhost/", mockRepository); mockRepository); var response = server.CreateGetRequest("api/categories"); var response = server.CreateGetRequest("api/categories"); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content);}
46
Integration testing a GET request: [TestMethod] public void GetAll_SingleCategory_StatusCodeOkAndNotNullContent() { var mockRepository = Mock.Create >(); var mockRepository = Mock.Create >(); var models = new List (); var models = new List (); models.Add(new Category() { Name = "Test Cat" }); models.Add(new Category() { Name = "Test Cat" }); Mock.Arrange(() => mockRepository.All()) Mock.Arrange(() => mockRepository.All()).Returns(() => models.AsQueryable());.Returns(() => models.AsQueryable()); var server = new InMemoryHttpServer ( var server = new InMemoryHttpServer ( "http://localhost/", "http://localhost/", mockRepository); mockRepository); var response = server.CreateGetRequest("api/categories"); var response = server.CreateGetRequest("api/categories"); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.IsNotNull(response.Content); Assert.IsNotNull(response.Content);} Fake in-memory server, that hosts the WebAPI controllers
47
Live Demo
48
форум програмиране, форум уеб дизайн курсове и уроци по програмиране, уеб дизайн – безплатно програмиране за деца – безплатни курсове и уроци безплатен SEO курс - оптимизация за търсачки уроци по уеб дизайн, HTML, CSS, JavaScript, Photoshop уроци по програмиране и уеб дизайн за ученици ASP.NET MVC курс – HTML, SQL, C#,.NET, ASP.NET MVC безплатен курс "Разработка на софтуер в cloud среда" BG Coder - онлайн състезателна система - online judge курсове и уроци по програмиране, книги – безплатно от Наков безплатен курс "Качествен програмен код" алго академия – състезателно програмиране, състезания ASP.NET курс - уеб програмиране, бази данни, C#,.NET, ASP.NET курсове и уроци по програмиране – Телерик академия курс мобилни приложения с iPhone, Android, WP7, PhoneGap free C# book, безплатна книга C#, книга Java, книга C# Николай Костов - блог за програмиране http://academy.Telerik.com
49
Develop a REST API for a BugLogger app Bugs have status: fixed, assigned, for-testing, pending Bugs have text and logDate Newly added bugs always have status "pending" Bugs can be queried – get all bugs, get bugs after a date, get only pending bugs, etc… 1. Develop a database in MS SQL Server that keeps the data of the bugs 2. Create repositories to work with the bugs database
50
3. Provide a REST API to work with the bugs Using WebAPI Provide the following actions: Log new bug Get all bugs Get bugs after a specific date: Get bugs by status Change bug status 4. Write unit tests to test the BugLogger Use a mocking framework 5. Write integration tests to test the BugLogger POST …/bugs GET …/bugs GET …/bugs?date=22-06-2014 GET …/bugs?type=pending PUT …/bugs/{id}
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.