ASP.NET कोर में मॉकिंग IPrincipal


91

मेरे पास ASP.NET MVC कोर एप्लीकेशन है जिसके लिए मैं यूनिट टेस्ट लिख रहा हूं। क्रिया विधियों में से एक उपयोगकर्ता का नाम कुछ कार्यक्षमता के लिए उपयोग करता है:

SettingsViewModel svm = _context.MySettings(User.Identity.Name);

जो स्पष्ट रूप से इकाई परीक्षण में विफल रहता है। मैंने चारों ओर देखा और सभी सुझाव .NET 4.5 से HttpContext की नकल करने के लिए हैं। मुझे यकीन है कि ऐसा करने का एक बेहतर तरीका है। मैंने इप्रिनिपल को इंजेक्ट करने की कोशिश की, लेकिन यह एक त्रुटि थी; और मैंने भी यह कोशिश की (हताशा से बाहर, मुझे लगता है):

public IActionResult Index(IPrincipal principal = null) {
    IPrincipal user = principal ?? User;
    SettingsViewModel svm = _context.MySettings(user.Identity.Name);
    return View(svm);
}

लेकिन इसने एक त्रुटि भी फेंक दी। डॉक्स में कुछ भी नहीं मिला ...

जवाबों:


182

कंट्रोलर को कंट्रोलर के माध्यम से एक्सेस किया जाता है । उत्तरार्द्ध भीतर संग्रहीत है ।User HttpContextControllerContext

उपयोगकर्ता का सेट करने का सबसे आसान तरीका एक निर्मित उपयोगकर्ता के साथ एक अलग HttpContext असाइन करना है। हम DefaultHttpContextइस उद्देश्य के लिए उपयोग कर सकते हैं , इस तरह से हमें सब कुछ नकली नहीं करना है। तब हम उस HttpContext का उपयोग एक नियंत्रक संदर्भ में करते हैं और उस नियंत्रक उदाहरण को पास करते हैं:

var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
    new Claim(ClaimTypes.Name, "example name"),
    new Claim(ClaimTypes.NameIdentifier, "1"),
    new Claim("custom-claim", "example claim value"),
}, "mock"));

var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
    HttpContext = new DefaultHttpContext() { User = user }
};

अपना खुद का निर्माण करते समय, निर्माणकर्ता को ClaimsIdentityएक स्पष्ट पास देना सुनिश्चित करें authenticationType। यह सुनिश्चित करता है कि IsAuthenticatedसही ढंग से काम करेगा (यदि आप अपने कोड में उपयोग करते हैं यह निर्धारित करने के लिए कि उपयोगकर्ता प्रमाणित है)।


7
मेरे मामले में यह new Claim(ClaimTypes.Name, "1")नियंत्रक उपयोग से मेल खाना था user.Identity.Name; लेकिन अन्यथा यह वास्तव में मैं क्या हासिल करने की कोशिश कर रहा था ... डेंके शॉन!
फेलिक्स

अनगिनत घंटों की खोज के बाद यह वह पोस्ट थी जिसने आखिरकार मुझे दूर कर दिया। मेरे कोर 2.0 प्रोजेक्ट कंट्रोलर मेथड में मैं User.FindFirstValue(ClaimTypes.NameIdentifier);एक ऑब्जेक्ट पर userId को सेट करने के लिए उपयोग कर रहा था जिसे मैं बना रहा था और असफल हो रहा था क्योंकि प्रिंसिपल शून्य था। यह मेरे लिए तय है। शानदार उत्तर के लिए धन्यवाद!
टिमोथी रान्डेल

मैं UserManager.GetUserAsync को काम करने के लिए अनगिनत घंटे भी खोज रहा था और यह एकमात्र स्थान है जो मुझे लापता लिंक मिला। धन्यवाद! क्लेम करने के लिए एक क्लेम करने की आवश्यकता है, जिसमें क्लेम को शामिल किया गया है, जेनरिकइंडेंटिटी का उपयोग नहीं किया गया है।
एटीन चारलैंड

17

पिछले संस्करणों में आप Userसीधे नियंत्रक पर सेट कर सकते थे , जो कुछ बहुत आसान इकाई परीक्षणों के लिए बनाया गया था।

यदि आप कंट्रोलरबेस के सोर्स कोड को देखते हैं, तो आप देखेंगे कि इससे Userनिकाला गया है HttpContext

/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;

और नियंत्रक के HttpContextमाध्यम से पहुँचता हैControllerContext

/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;

आप देखेंगे कि इन दोनों को केवल गुण पढ़ा जाता है। अच्छी खबर यह है कि ControllerContextसंपत्ति इसकी कीमत निर्धारित करने की अनुमति देती है ताकि आपका रास्ता इसमें हो।

तो लक्ष्य उस वस्तु को प्राप्त करना है। कोर HttpContextमें अमूर्त है, इसलिए इसे मॉक करना बहुत आसान है।

जैसे एक नियंत्रक मान

public class MyController : Controller {
    IMyContext _context;

    public MyController(IMyContext context) {
        _context = context;
    }

    public IActionResult Index() {
        SettingsViewModel svm = _context.MySettings(User.Identity.Name);
        return View(svm);
    }

    //...other code removed for brevity 
}

Moq का उपयोग करना, एक परीक्षण इस तरह दिख सकता है

public void Given_User_Index_Should_Return_ViewResult_With_Model() {
    //Arrange 
    var username = "FakeUserName";
    var identity = new GenericIdentity(username, "");

    var mockPrincipal = new Mock<ClaimsPrincipal>();
    mockPrincipal.Setup(x => x.Identity).Returns(identity);
    mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

    var mockHttpContext = new Mock<HttpContext>();
    mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

    var model = new SettingsViewModel() {
        //...other code removed for brevity
    };

    var mockContext = new Mock<IMyContext>();
    mockContext.Setup(m => m.MySettings(username)).Returns(model);

    var controller = new MyController(mockContext.Object) {
        ControllerContext = new ControllerContext {
            HttpContext = mockHttpContext.Object
        }
    };

    //Act
    var viewResult = controller.Index() as ViewResult;

    //Assert
    Assert.IsNotNull(viewResult);
    Assert.IsNotNull(viewResult.Model);
    Assert.AreEqual(model, viewResult.Model);
}

3

मौजूदा कक्षाओं का उपयोग करने की संभावना भी है, और केवल जब आवश्यक हो तो मॉक करें।

var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = user.Object
    }
};

3

मेरे मामले में, मैं का उपयोग करने की जरूरत है Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Nameऔर कुछ व्यापार तर्क नियंत्रक के बाहर बैठा। मैं इसके लिए नकोसी, कैलिन और पोक के उत्तर के संयोजन का उपयोग करने में सक्षम था:

var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");

var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);

var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();

var controller = new MyController(...);

var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
    User = mockPrincipal.Object
};

var result = controller.Get() as OkObjectResult;
//Assert results

mockAuthHandler.Verify();

2

मैं एक सार फैक्टरी पैटर्न को लागू करने के लिए देखूंगा।

विशेष रूप से उपयोगकर्ता नाम प्रदान करने के लिए एक कारखाने के लिए एक इंटरफ़ेस बनाएँ।

फिर ठोस कक्षाएं प्रदान करें User.Identity.Name, जो एक प्रदान करता है , और एक जो कुछ अन्य कठिन कोडित मूल्य प्रदान करता है जो आपके परीक्षणों के लिए काम करता है।

फिर आप उत्पादन बनाम परीक्षण कोड के आधार पर उपयुक्त ठोस वर्ग का उपयोग कर सकते हैं। शायद कारखाने को एक पैरामीटर के रूप में पारित करना, या कुछ कॉन्फ़िगरेशन मान के आधार पर सही कारखाने पर स्विच करना।

interface IUserNameFactory
{
    string BuildUserName();
}

class ProductionFactory : IUserNameFactory
{
    public BuildUserName() { return User.Identity.Name; }
}

class MockFactory : IUserNameFactory
{
    public BuildUserName() { return "James"; }
}

IUserNameFactory factory;

if(inProductionMode)
{
    factory = new ProductionFactory();
}
else
{
    factory = new MockFactory();
}

SettingsViewModel svm = _context.MySettings(factory.BuildUserName());

धन्यवाद। मैं अपनी वस्तुओं के लिए कुछ ऐसा ही कर रहा हूं । मैं बस उम्मीद कर रहा था कि इप्रीनिकपाल जैसी सामान्य चीज के लिए "आउट ऑफ द बॉक्स" कुछ होगा। लेकिन जाहिर है, नहीं!
फेलिक्स

इसके अतिरिक्त, उपयोगकर्ता कंट्रोलरबेस का सदस्य चर है। यही कारण है कि ASP.NET के पुराने संस्करणों में लोगों ने HttpContext का मज़ाक उड़ाया, और वहाँ से IPrincipal प्राप्त कर रहे थे। केवल एक स्टैंडअलोन वर्ग से उपयोगकर्ता प्राप्त नहीं कर सकता, जैसे प्रोडक्शनफैक्ट्री
फेलिक्स

1

मैं अपने नियंत्रकों को सीधे मारना चाहता हूं और बस ऑटोफेक जैसे डि का उपयोग करता हूं। ऐसा करने के लिए मैं पहली बार पंजीकरण कर रहा हूं ContextController

var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
    User = new GenericPrincipal(identity, null)
};

var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);

जब मैं नियंत्रकों को पंजीकृत करता हूं तो अगला मैं संपत्ति इंजेक्शन सक्षम करता हूं।

  builder.RegisterAssemblyTypes(assembly)
                    .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();

तब User.Identity.Nameआबादी है, और मुझे अपने नियंत्रक पर एक विधि को कॉल करते समय कुछ विशेष करने की आवश्यकता नहीं है।

public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
    var requestedBy = User.Identity?.Name;
    ..................
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.