Skip to content

Commit 14496df

Browse files
committed
Merge pull request #218 from ivaylokenov/development
Version 1.1.5
2 parents 3d60b66 + 6c9d0e2 commit 14496df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+592
-104
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Please see the [documentation](https://github.com/ivaylokenov/MyWebApi/tree/mast
1111

1212
## Installation
1313

14-
You can install this library using NuGet into your Test class project. It will automatically reference the needed dependencies of Microsoft.AspNet.WebApi.Core (≥ 5.1.0), Microsoft.Owin.Testing (≥ 3.0.1) and Microsoft.Owin.Host.HttpListener (≥ 3.0.1) for you. .NET 4.5+ is needed. Make sure your solution has the same versions of the mentioned dependencies in all projects where you are using them. For example, if you are using Microsoft.AspNet.WebApi.Core 5.2.3 in your Web project, the same version should be used after installing MyWebApi in your Tests project.
14+
You can install this library using NuGet into your Test class project. It will automatically reference the needed dependencies of Microsoft.AspNet.WebApi.Core (≥ 5.1.0) and Microsoft.Owin.Testing (≥ 3.0.1) for you. .NET 4.5+ is needed. Make sure your solution has the same versions of the mentioned dependencies in all projects where you are using them. For example, if you are using Microsoft.AspNet.WebApi.Core 5.2.3 in your Web project, the same version should be used after installing MyWebApi in your Tests project.
1515

1616
Install-Package MyWebApi
1717

documentation/README.md

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
- Integration testing of the full server pipeline
4545
- [HTTP server](#http-server)
4646
- [OWIN pipeline](#owin-pipeline)
47-
- Additional methods
47+
- Additional classes and methods
48+
- [Helper classes](#helper-classes)
4849
- [AndProvide... methods](#andprovide-methods)
4950

5051
### Using custom HttpConfiguration
@@ -63,6 +64,13 @@ MyWebApi.IsRegisteredWith(WebApiConfig.Register);
6364
// * it is useful if you want to reset the global
6465
// * configuration used in other tests
6566
MyWebApi.IsUsingDefaultHttpConfiguration();
67+
68+
// the three options provide a way to set
69+
// the error detail policy for the testing
70+
// * default is 'Always' for easier debugging
71+
MyWebApi
72+
.IsRegisteredWith(WebApiConfig.Register)
73+
.WithErrorDetailPolicy(IncludeErrorDetailPolicy.LocalOnly);
6674
```
6775

6876
[To top](#table-of-contents)
@@ -565,7 +573,7 @@ MyWebApi
565573
MyWebApi
566574
.Controller<WebApiController>()
567575
.WithHttpRequestMessage(request => request
568-
.WithHeader("SomeHeader", "SomeHeaderValue"));
576+
.WithHeader(HttpHeader.Accept, MediaType.TextHtml));
569577

570578
// adding custom header with multiple values to the request message
571579
MyWebApi
@@ -584,7 +592,7 @@ MyWebApi
584592
MyWebApi
585593
.Controller<WebApiController>()
586594
.WithHttpRequestMessage(request => request
587-
.WithContentHeader("SomeContentHeader", "SomeContentHeaderValue"));
595+
.WithContentHeader(HttpContentHeader.ContentType, MediaType.ApplicationJson));
588596

589597
// adding custom content header with multiple values to the request message
590598
// * adding content headers requires content to be initialized and set
@@ -726,6 +734,12 @@ MyWebApi
726734
MyWebApi
727735
.Controller<WebApiController>()
728736
.CallingAsync(c => c.SomeActionAsync());
737+
738+
// if action has non-important parameters, instead of adding dummy values
739+
// use With.Any<TParameter> for better readability
740+
MyWebApi
741+
.Controller<WebApiController>()
742+
.Calling(c => c.SomeAction(With.Any<int>()));
729743
```
730744
[To top](#table-of-contents)
731745

@@ -844,6 +858,15 @@ MyWebApi
844858
.ShouldHave()
845859
.ActionAttributes();
846860

861+
// since testing for attributes does not require valid parameters,
862+
// instead of adding dummy values
863+
// use With.Any<TParameter> for better readability
864+
MyWebApi
865+
.Controller<WebApiController>()
866+
.Calling(c => c.SomeAction(With.Any<int>()))
867+
.ShouldHave()
868+
.ActionAttributes();
869+
847870
// tests whether action has specific number of attributes
848871
MyWebApi
849872
.Controller<WebApiController>()
@@ -2526,9 +2549,8 @@ MyWebApi.Server().Stops();
25262549
You can test over the full pipeline by providing OWIN start up class and optional network host and post. You can start global HTTP server in your test/class/assembly initialize method and set test cases with different requests or just instantiate separate server for each test:
25272550

25282551
```c#
2529-
// starts OWIN web server with the provided host and port. If such are not provided, default is "http://localhost:1234"
2552+
// starts OWIN web server with the provided host and port. If such are not provided, default is "http://localhost:80"
25302553
// * the server is disposed after the test
2531-
// * some hosts and ports may require administrator rights
25322554
// * HTTP request can be set just like in the controller unit tests
25332555
// * HTTP response can be tested just like in the controller unit tests
25342556
MyWebApi
@@ -2544,7 +2566,7 @@ MyWebApi
25442566
// starts OWIN server with specific
25452567
// for the test Startup class
25462568
// * the server is disposed after the test
2547-
// * since host and port are not provided, the default "http://localhost:1234" is used
2569+
// * since host and port are not provided, the default "http://localhost:80" is used
25482570
MyWebApi
25492571
.Server()
25502572
.Working<Startup>() // working will instantiate new OWIN server with the specified Startup class
@@ -2568,13 +2590,77 @@ MyWebApi
25682590
// more test cases on the same global server
25692591
25702592
// stops the global OWIN server
2593+
MyWebApi.Server().Stops();
2594+
2595+
// saving the server builder instance for later usage
2596+
// * can be done with the normal HTTP server too
2597+
var server = MyWebApi.Server().Starts<Startup>();
2598+
2599+
server
2600+
.WithHttpRequestMessage(httpRequestMessage)
2601+
.ShouldReturnHttpResponseMessage()
2602+
.WithStatusCode(HttpStatusCode.OK);
2603+
2604+
// more test cases on the same global server
2605+
25712606
MyWebApi.Server().Stops();
25722607
```
25732608

25742609
Summary - the **".Working()"** method without parameters will check if the global OWIN server is started. If not, it will check whether a global HTTP server is started. If not, it will instantiate new HTTP server using the global HTTP configuration. The first match will process the request and test over the response. If no server can be started, exception will be thrown. Using **".Working(config)"** will start new HTTP server with the provided configuration and dispose it after the test. Using **".Working<Startup>()"** will start new OWIN server with the provided start up class and dispose it after the test. Global server can be started with **"MyWebApi.Server().Starts()"** and it will be HTTP or OWIN dependending on the parameters. Global servers can be stopped with **"MyWebApi.Server().Stops()"**, no matter HTTP or OWIN.
25752610

25762611
[To top](#table-of-contents)
25772612

2613+
### Helper classes
2614+
2615+
The library gives you helper classes for common magic strings and non-important action call parameters:
2616+
2617+
```c#
2618+
// With.Any<TParameter> is helpful where action call parameter values are not important
2619+
MyWebApi
2620+
.Controller<WebApiController>()
2621+
.Calling(c => c.SomeAction(With.Any<int>()))
2622+
.ShouldHave()
2623+
.ActionAttributes();
2624+
2625+
// MediaType class contains common media type strings
2626+
MyWebApi
2627+
.Controller<WebApiController>()
2628+
.Calling(c => c.SomeAction())
2629+
.ShouldReturn()
2630+
.Content()
2631+
.WithMediaType(MediaType.ApplicationJson); // represents "application/json"
2632+
2633+
// HttpHeader class contains common HTTP header names
2634+
MyWebApi
2635+
.Controller<WebApiController>()
2636+
.WithHttpRequestMessage(
2637+
request => request
2638+
.WithHeader(HttpHeader.Accept, MediaType.ApplicationJson)) // represents "Accept" HTTP header
2639+
.Calling(c => c.SomeAction())
2640+
.ShouldReturn()
2641+
.Ok();
2642+
2643+
// HttpContentHeader class contains common HTTP content header names
2644+
MyWebApi
2645+
.Controller<WebApiController>()
2646+
.WithHttpRequestMessage(
2647+
request => request
2648+
.WithContentHeader(HttpContentHeader.ContentType, MediaType.ApplicationJson)) // represents "ContentType" HTTP header
2649+
.Calling(c => c.SomeAction())
2650+
.ShouldReturn()
2651+
.Ok();
2652+
2653+
// AuthenticationScheme class containing common authentication schemes
2654+
MyWebApi
2655+
.Controller<WebApiController>()
2656+
.Calling(c => c.UnauthorizedActionWithChallenges())
2657+
.ShouldReturn()
2658+
.Unauthorized()
2659+
.ContainingAuthenticationHeaderChallenge(AuthenticationScheme.Basic); // represents Basic authentication scheme
2660+
```
2661+
2662+
[To top](#table-of-contents)
2663+
25782664
### AndProvide... methods
25792665

25802666
You can get different Web API specific objects used in the test case where applicable by using AndProvide... methods.

samples/Books Web API/Books.Api/App_Start/IdentityConfig.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Books.Api
99
{
1010
using Books.Models;
1111
using Data;
12+
using Infrastructure;
1213

1314
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
1415

@@ -21,7 +22,9 @@ public ApplicationUserManager(IUserStore<Author> store)
2122

2223
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
2324
{
24-
var manager = new ApplicationUserManager(new UserStore<Author>(context.Get<BooksDbContext>()));
25+
var manager = ObjectFactory.TryGetInstance<ApplicationUserManager>() ??
26+
new ApplicationUserManager(new UserStore<Author>(context.Get<BooksDbContext>()));
27+
2528
// Configure validation logic for usernames
2629
manager.UserValidator = new UserValidator<Author>(manager)
2730
{
@@ -32,10 +35,10 @@ public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUs
3235
manager.PasswordValidator = new PasswordValidator
3336
{
3437
RequiredLength = 6,
35-
RequireNonLetterOrDigit = true,
36-
RequireDigit = true,
37-
RequireLowercase = true,
38-
RequireUppercase = true,
38+
RequireNonLetterOrDigit = false,
39+
RequireDigit = false,
40+
RequireLowercase = false,
41+
RequireUppercase = false,
3942
};
4043
var dataProtectionProvider = options.DataProtectionProvider;
4144
if (dataProtectionProvider != null)

samples/Books Web API/Books.Api/App_Start/NinjectConfig.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Books.Api
66
using System;
77
using System.Web;
88
using Data;
9+
using Infrastructure;
910
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
1011

1112
using Ninject;
@@ -15,7 +16,7 @@ public static class NinjectConfig
1516
{
1617
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
1718

18-
public static Action<IKernel> RebindAction { get; set; }
19+
public static Action<IKernel> RebindAction { get; set; } // should be used only in integration testing scenarios
1920

2021
/// <summary>
2122
/// Starts the application
@@ -53,6 +54,7 @@ public static IKernel CreateKernel()
5354
RebindAction(kernel);
5455
}
5556

57+
ObjectFactory.Initialize(kernel);
5658
return kernel;
5759
}
5860
catch

samples/Books Web API/Books.Api/Books.Api.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@
245245
<Compile Include="Global.asax.cs">
246246
<DependentUpon>Global.asax</DependentUpon>
247247
</Compile>
248+
<Compile Include="Infrastructure\ObjectFactory.cs" />
248249
<Compile Include="Mappings\IHaveCustomMappings.cs" />
249250
<Compile Include="Mappings\IMapFrom.cs" />
250251
<Compile Include="Models\AccountBindingModels.cs" />

samples/Books Web API/Books.Api/Controllers/AccountController.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,9 @@ public AccountController()
3232
{
3333
}
3434

35-
public AccountController(ApplicationUserManager userManager,
36-
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
35+
public AccountController(ApplicationUserManager userManager)
3736
{
3837
UserManager = userManager;
39-
AccessTokenFormat = accessTokenFormat;
4038
}
4139

4240
public ApplicationUserManager UserManager
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Books.Api.Infrastructure
2+
{
3+
using Ninject;
4+
5+
public static class ObjectFactory
6+
{
7+
public static IKernel standartKernel;
8+
9+
public static void Initialize(IKernel kernel)
10+
{
11+
standartKernel = kernel;
12+
}
13+
14+
public static T TryGetInstance<T>()
15+
{
16+
return standartKernel.TryGet<T>();
17+
}
18+
}
19+
}

samples/Books Web API/Books.Api/Startup.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ public void Configuration(IAppBuilder app)
1616
{
1717
AutoMapperConfig.RegisterMappings(Assembly.GetExecutingAssembly());
1818

19+
app.UseNinjectMiddleware(NinjectConfig.CreateKernel);
20+
1921
ConfigureAuth(app);
2022

2123
var config = new HttpConfiguration();
2224

2325
WebApiConfig.Register(config);
2426

25-
app
26-
.UseNinjectMiddleware(NinjectConfig.CreateKernel)
27-
.UseNinjectWebApi(config);
27+
app.UseNinjectWebApi(config);
2828
}
2929
}
3030
}

samples/Books Web API/Books.Api/Web.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
1010
</configSections>
1111
<connectionStrings>
12-
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-Books.Api-20150901121637.mdf;Initial Catalog=aspnet-Books.Api-20150901121637;Integrated Security=True" providerName="System.Data.SqlClient" />
12+
<add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=BooksTestingApp;Integrated Security=True" providerName="System.Data.SqlClient" />
1313
</connectionStrings>
1414
<appSettings></appSettings>
1515
<system.web>

samples/Books Web API/Books.Tests/ApiTests/IntegrationTests/BooksControllerIntegrationTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
using Api;
77
using Api.Models.ResponseModels;
88
using Data;
9+
using Microsoft.Owin.Security;
910
using Mocks;
1011
using Models;
1112
using MyTested.WebApi;
1213
using MyTested.WebApi.Builders.Contracts.Servers;
14+
using Newtonsoft.Json.Linq;
1315
using NUnit.Framework;
1416

1517
[TestFixture]
1618
public class BooksControllerIntegrationTests
1719
{
1820
private IServerBuilder server;
21+
private string accessToken;
1922

2023
[TestFixtureSetUp]
2124
public void Init()
@@ -24,10 +27,13 @@ public void Init()
2427
{
2528
kernel.Rebind<IRepository<Book>>().ToConstant(MocksFactory.BooksRepository);
2629
kernel.Rebind<IRepository<Author>>().ToConstant(MocksFactory.AuthorsRepository);
30+
kernel.Rebind<ApplicationUserManager>().ToConstant(MocksFactory.ApplicationUserManager);
2731
};
2832

2933
MyWebApi.Server().Starts<Startup>();
3034
this.server = MyWebApi.Server().Working();
35+
36+
this.GetAccessToken();
3137
}
3238

3339
[Test]
@@ -43,10 +49,38 @@ public void BooksControllerShouldReturnCorrectBooksForUnauthorizedUsers()
4349
.Passing(m => m.Count == 3);
4450
}
4551

52+
[Test]
53+
public void BooksControllerShouldReturnCorrectBooksForAuthorizedUsers()
54+
{
55+
server
56+
.WithHttpRequestMessage(req => req
57+
.WithRequestUri("/api/Books/Get")
58+
.WithMethod(HttpMethod.Get)
59+
.WithHeader(HttpHeader.Authorization, "Bearer " + this.accessToken))
60+
.ShouldReturnHttpResponseMessage()
61+
.WithStatusCode(HttpStatusCode.OK)
62+
.WithResponseModelOfType<List<BookResponseModel>>()
63+
.Passing(m => m.Count == 10);
64+
}
65+
4666
[TestFixtureTearDown]
4767
public void TearDown()
4868
{
4969
MyWebApi.Server().Stops();
5070
}
71+
72+
private void GetAccessToken()
73+
{
74+
var message = server
75+
.WithHttpRequestMessage(req => req
76+
.WithRequestUri("/token")
77+
.WithMethod(HttpMethod.Post)
78+
.WithFormUrlEncodedContent("username=TestAuthor@test.com&password=testpass&grant_type=password"))
79+
.ShouldReturnHttpResponseMessage()
80+
.AndProvideTheHttpResponseMessage();
81+
82+
var result = JObject.Parse(message.Content.ReadAsStringAsync().Result);
83+
this.accessToken = (string)result["access_token"];
84+
}
5185
}
5286
}

0 commit comments

Comments
 (0)