I am fan of
TDD principle, and always trying to write tests first. Today I want to show,
how I usually test Web API controller. There are two approaches:
1. Unit test
Consider
UserController class, which has POST method that registers users.
public class UserController : ApiController
{
private readonly IRegistrationService registrationService;
public
UserController(IRegistrationService registrationService)
{
this.registrationService = registrationService;
}
public HttpResponseMessage Post([FromBody]string json)
{
var model = new JavaScriptSerializer().Deserialize<RegistrationModel>(json);
bool success = this.registrationService.Register(model);
return new HttpResponseMessage(success ? HttpStatusCode.OK : HttpStatusCode.InternalServerError);
}
And test
(which, of course, was written first)
[Test]
public void TestUserCanBeRegistered()
{
// Arrange
var
registrationService = new Mock<IRegistrationService>();
registrationService
.Setup(x => x.Register(It.IsAny<RegistrationModel>()))
.Returns(true);
var
userController = new UserController(registrationService.Object);
// Act
var response = userController.Post(@"{""login"":""Alex"", ""password"": ""password""}");
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
Test runs
OK
BUT if you
try to send real request through the fiddler, for example, you will get an error.
Content-Type:
application/json; charset=utf-8
Content-Length:
40
{"login":"Alex",
"password": "password"}
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json;
charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-SourceFiles:
=?UTF-8?B?RTpcVGVtcFxXZWJBcHBcV2ViQXBwXHVzZXJc?=
X-Powered-By: ASP.NET
Date: Thu, 12 Jan 2017 12:57:07 GMT
Content-Length: 2339
{"Message":"An
error has occurred.","ExceptionMessage":"Value
cannot be null.\r\nParameter name: input","ExceptionType":"System.ArgumentNullException","StackTrace":" at System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer
serializer, String input, Type type, Int32 depthLimit)
How we can
get it, if test passed OK? This happens, because we fill method parameters by
ourselves, not by real request. Error in
tests costs much more than in the code, that's why I prefer another method of
controller testing
2. Integration test*
Let’s test it in the other manner.
First, we need to add some references to our project with tests.
First, we need to add some references to our project with tests.
Install-Package
Microsoft.Owin.Host.SystemWeb
Install-Package
Microsoft.AspNet.WebApi.Owin
Install-Package
Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package
Microsoft.Owin.Testing
Install-Package Unity
For our convenience we create base class for
integration tests
internal class IntegrationTestBase
{
protected TestServer Server { get; private set; }
[TestFixtureSetUp]
public void BindServices()
{
this.Server = TestServer.Create<TestStartup>();
}
[TestFixtureTearDown]
public void DisposeServer()
{
this.Server.Dispose();
}
}
TestStartup.cs. If you are hosting
your application with OWIN you can use Startup.cs here.
public class TestStartup
{
public void Configuration(IAppBuilder builder)
{
config.Routes.MapHttpRoute("users", "users", new { controller = "User" });
var container = new UnityContainer();
container.RegisterType<IRegistrationService, RegistrationService>();
config.DependencyResolver = new UnityResolver(container);
builder.UseWebApi(config);
}
}
And finally write new test using
OWIN TestServer that will be hosted InProc.
[Test]
public async void TestRequestWasMappedCorrectlyAndUserRegistered()
{
// Arrange
using (var client = new HttpClient(this.Server.Handler))
{
var body = new StringContent(@"{""login"":""Alex"", ""password"": ""password""}", Encoding.UTF8, "application/json");
// Act
var response = await client.PostAsync("http://api.webapp.com/users/", body);
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
This approach simulates real request to the API resource from the client. Test didn’t pass. Now this correlates with the real behavior. Time to “green” this test. There are many ways to do it:
1. Change type
of the input parameter to HttpRequestMessage
public HttpResponseMessage Post(HttpRequestMessage request) { … }
and then
gets the content
string body =
message.Content.ReadAsStringAsync().Result;
2. Change
type of the input parameter to IDictionary<string, string> (using
IDictionary<string, object> is not recommended)
public HttpResponseMessage Post(IDictionary<string, string> dict) { … }
3. Or
specify concrete type RegistrationModel
4. Or vice versa
make it more generic with dynamic
public HttpResponseMessage Post([FromBody]dynamic obj) { … }
I chose
third variant, so our controller method will look like
public HttpResponseMessage Post(RegistrationModel model)
{
bool success = this.registrationService.Register(model);
return new HttpResponseMessage(success ? HttpStatusCode.OK : HttpStatusCode.InternalServerError);
}
Now all
tests pass OK (of course we should fix test ‘TestUserCanBeRegistered’, because
we changed the contract).
Example of the code available on github
* - I name it integration, because it checks not only controller class but also MediaTypeFormatter
Pretty blog, so many ideas in a single site, thanks for the informative article, keep updating more article.
ReplyDeleteSoftware testing course in chennai
Learned a lot from your post and it is really good. Share more tech updates regularly.
ReplyDeleteui ux design course in Chennai
ui design course in chennai
ui developer course in chennai
Ethical Hacking course in Chennai
Web Designing Course in chennai
Web development training in chennai
PHP Training in Chennai
ui ux design course in Anna Nagar
ui ux design course in Vadapalani
ui ux design course in Thiruvanmiyur
smm panel
ReplyDeletesmm panel
iş ilanları
İnstagram Takipçi Satın Al
hırdavatçı burada
Www.beyazesyateknikservisi.com.tr
Servis
tiktok jeton hilesi