2014-05-07 16 views
5

Chcę utworzyć instancję formularza FormsAuthenticationTicket (nad którą nie mam kontroli, część System.Web.Security) za pomocą AutOstalacji i upewniając się, że UserData (typu string) zawiera prawidłowy ciąg znaków XMLUtwórz instancję FormsAuthenticationTicket z poprawnym ciągiem XML w UserData

var testTicket = fixture.Create<FormsAuthenticationTicket>(); 

problemem jest to, że UserData można ustawić tylko przy uruchamianiu tego obiektu za pomocą poniższego konstruktora:

public FormsAuthenticationTicket(int version, string name, DateTime issueDate, DateTime expiration, bool isPersistent, string userData); 

Gdzie "userData" jest prawidłowy ciąg XML.

Mogę skonfigurować ten typ, aby użyć najbardziej chciwego konstruktora, ale to nie rozwiązuje problemu dostarczenia prawidłowego łańcucha XML do userData.

Mogę zatrzymać typ łańcucha, aby zawsze zwracał prawidłowy ciąg znaków XML, ale zależy mi również na innych wartościach ciągu w teście.

Myślę, że możliwym podejściem jest dostosowanie algorytmu do generowania ciągów ... ale nie mam parametrów, aby wiedzieć, kiedy podać ciąg znaków XML.

Odpowiedz

2

AutoFixture wybiera skromne konstruktora (domyślnie) i od czasu userData nie jest częścią skromnym konstruktora musimy dostosować dwa rzeczy tutaj:

  1. Zmień strategia konstruktora dla pojedynczego typu (FormsAuthenticationTicket).
  2. Podaj niestandardową wartość argumentu konstruktora userData.

Poniżej dostosowywania obejmuje zarówno:

internal class UserDataCustomization : ICustomization 
{ 
    private readonly string userData; 

    public UserDataCustomization(string userData) 
    { 
     this.userData = userData; 
    } 

    public void Customize(IFixture fixture) 
    { 
     fixture.Customize<FormsAuthenticationTicket>(c => 
      c.FromFactory(
       new MethodInvoker(
        new GreedyConstructorQuery()))); 
     fixture.Customizations.Add(new UserDataBuilder(this.userData)); 
    } 

    private class UserDataBuilder : ISpecimenBuilder 
    { 
     private readonly string userData; 

     public UserDataBuilder(string userData) 
     { 
      this.userData = userData; 
     } 

     public object Create(object request, ISpecimenContext context) 
     { 
      var pi = request as ParameterInfo; 
      if (pi != null && pi.Name == "userData") 
       return this.userData; 

      return new NoSpecimen(); 
     } 
    } 
} 

Poniższe badanie przechodzi:

[Fact] 
public void UserDataIsCorrect() 
{ 
    var expected = "<foo></foo>"; 
    var fixture = new Fixture(); 
    fixture.Customize(new UserDataCustomization(expected)); 

    var actual = fixture.Create<FormsAuthenticationTicket>(); 

    Assert.Equal(expected, actual.UserData); 
} 

nadziei, że pomaga.


FWIW, jest tu również takie same w F #:

open Ploeh.AutoFixture 
open Ploeh.AutoFixture.Kernel 
open System 
open System.Reflection 
open System.Web.Security 

type UserDataCustomization (userData) = 
    let builder = { 
     new ISpecimenBuilder with 
      member this.Create(request, context) = 
       match request with 
       | :? ParameterInfo as pi 
        when pi.Name = "userData" -> box userData 
       | _ -> NoSpecimen request |> box } 

    interface ICustomization with 
     member this.Customize fixture = 
      fixture.Customize<FormsAuthenticationTicket>(fun c -> 
       c.FromFactory(
        MethodInvoker(
         GreedyConstructorQuery())) :> ISpecimenBuilder) 
      fixture.Customizations.Add builder 

Poniższy test zakończy się pomyślnie:

open Xunit 
open Swensen.Unquote.Assertions 

[<Fact>] 
let UserDataIsCorrect() = 
    let expected = "<foo></foo>" 
    let fixture = Fixture().Customize(UserDataCustomization(expected)) 

    let actual = fixture.Create<FormsAuthenticationTicket>() 

    test <@ expected = actual.UserData @> 
2

Zamrożenie łańcucha będzie działać, ale nie jest pożądane, ponieważ wpłynie również na wszystkie inne wygenerowane ciągi.

Używam tej klasy, aby dostosować konkretnego argumentu konstruktora:

public class GenericArgCustomization<T> : ISpecimenBuilder 
{ 
    private readonly string name; 
    private readonly T value; 

    public GenericArgCustomization(string name, T value) 
    { 
     if (String.IsNullOrEmpty(name)) 
      throw new ArgumentException("Name is required", "name"); 

     this.name = name; 
     this.value = value; 
    } 

    public object Create(object request, ISpecimenContext context) 
    { 
     var pi = request as ParameterInfo; 
     if (pi == null) 
      return new NoSpecimen(request); 

     if (pi.ParameterType != typeof(T) || pi.Name != this.name) 
      return new NoSpecimen(request); 

     return this.value; 
    } 
} 

Następnie można użyć go poprzez określenie typu rodzajowego (string w tym przypadku) i nazwę argument konstruktora, który chcesz dostosować (userData w tym przypadku). Rozróżniana jest wielkość liter, więc pasuje do nazwy argumentu użytej w klasie.

Przykład:

var xml = "<root>My Valid XML Value Here</root>"; 
var customUserData = new GenericArgCustomization<string>("userData", xml); 
var fixture = new Fixture(); 
fixture.Customizations.Add(customUserData); 
var item = fixture.Create<FormsAuthenticationTicket>(); 

Teraz pozycja zostanie utworzona z danymi takimi jak:

  • Wersja: 85
  • Nazwa: name1e8fb8b1-5879-4256-8729-ca6afeb1bd80
  • IssueDate: 12/3/2015 8:00:05 AM
  • IsPersistent: True
  • UserData: <root>My Valid XML Value Here</root>
+1

Mimo to dobrze wyglądać jako uogólnionego podejścia, w tym konkretnym przypadku nie będzie działać, ponieważ 'userData' nie jest częścią skromnego konstruktora. –

+0

@ Nikos to uczciwe do wskazania. OP oświadczył, że jest zaznajomiony z konfiguracją chciwego dostosowywania konstruktorów, więc nie podkreśliłem tej części. –

+1

Zgoda. Ale ja osobiście preferuję odpowiedzi autonomiczne, w których można po prostu skopiować i wkleić odpowiedź i użyć jej. Użytkownicy rzadko czytają strony internetowe słowo po słowie; zamiast tego [zeskanują stronę] (http://www.nngroup.com/articles/how-users-read-on-the-web/). –