Mock HttpContext.Current dalam Metode Test Init

176

Saya mencoba menambahkan pengujian unit ke aplikasi ASP.NET MVC yang telah saya buat. Dalam pengujian unit saya, saya menggunakan kode berikut:

[TestMethod]
public void IndexAction_Should_Return_View() {
    var controller = new MembershipController();
    controller.SetFakeControllerContext("TestUser");

    ...
}

Dengan bantuan berikut untuk mengejek konteks controller:

public static class FakeControllerContext {
    public static HttpContextBase FakeHttpContext(string username) {
        var context = new Mock<HttpContextBase>();

        context.SetupGet(ctx => ctx.Request.IsAuthenticated).Returns(!string.IsNullOrEmpty(username));

        if (!string.IsNullOrEmpty(username))
            context.SetupGet(ctx => ctx.User.Identity).Returns(FakeIdentity.CreateIdentity(username));

        return context.Object;
    }

    public static void SetFakeControllerContext(this Controller controller, string username = null) {
        var httpContext = FakeHttpContext(username);
        var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
        controller.ControllerContext = context;
    }
}

Kelas tes ini mewarisi dari kelas dasar yang memiliki yang berikut:

[TestInitialize]
public void Init() {
    ...
}

Di dalam metode ini ia memanggil perpustakaan (yang saya tidak punya kendali atas) yang mencoba menjalankan kode berikut:

HttpContext.Current.User.Identity.IsAuthenticated

Sekarang Anda mungkin dapat melihat masalahnya. Saya telah mengatur HttpContext palsu terhadap controller tetapi tidak dalam metode dasar Init ini. Unit testing / ejekan sangat baru bagi saya jadi saya ingin memastikan saya mendapatkan ini dengan benar. Apa cara yang benar bagi saya untuk mengejek HttpContext sehingga dibagi di controller saya dan semua perpustakaan yang disebut dalam metode Init saya.

jangan pergi
sumber

Jawaban:

362

HttpContext.Currentmengembalikan sebuah instance dari System.Web.HttpContext, yang tidak meluas System.Web.HttpContextBase. HttpContextBaseditambahkan kemudian untuk mengatasi HttpContextmenjadi sulit untuk diejek. Kedua kelas pada dasarnya tidak terkait ( HttpContextWrapperdigunakan sebagai adaptor di antara mereka).

Untungnya, HttpContextitu sendiri palsu hanya cukup untuk Anda mengganti IPrincipal(Pengguna) dan IIdentity.

Kode berikut berjalan seperti yang diharapkan, bahkan dalam aplikasi konsol:

HttpContext.Current = new HttpContext(
    new HttpRequest("", "http://tempuri.org", ""),
    new HttpResponse(new StringWriter())
    );

// User is logged in
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity("username"),
    new string[0]
    );

// User is logged out
HttpContext.Current.User = new GenericPrincipal(
    new GenericIdentity(String.Empty),
    new string[0]
    );
Richard Szalay
sumber
Ceria tapi bagaimana saya bisa mengatur ini untuk pengguna yang keluar?
nfplee
5
@ nfplee - Jika Anda memasukkan string kosong ke GenericIdentitykonstruktor, IsAuthenticatedakan kembali salah
Richard Szalay
2
Bisakah ini digunakan untuk mengejek Cache di HttpContext?
DevDave
1
Ya bisa. Terima kasih!
DevDave
4
@CiaranG - Penggunaan MVC HttpContextBase, yang bisa diejek. Tidak perlu menggunakan solusi yang saya posting jika Anda menggunakan MVC. Jika Anda terus melakukannya, Anda mungkin perlu menjalankan kode yang saya posting sebelum Anda bahkan membuat controller.
Richard Szalay
35

Di bawah Test Init juga akan melakukan pekerjaan.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
  YourControllerToBeTestedController = GetYourToBeTestedController();
}
PUG
sumber
Saya tidak bisa mendapatkan addressibility ke HTTPContext dalam proyek Tes terpisah dalam solusi saya. Apakah Anda bisa mendapatkannya melalui mewarisi pengontrol?
John Peters
1
Apakah Anda memiliki referensi System.Webdalam proyek pengujian Anda?
PUG
Ya tapi proyek saya adalah proyek MVC, mungkinkah versi MVC dari System.Web hanya berisi subset dari namespace itu?
John Peters
2
@ user1522548 (Anda harus membuat akun) Assembly System.Web.dll, v4.0.0.0 pasti memiliki HTTPContext saya baru saja memeriksa kode sumber saya.
PUG
Kesalahan saya, saya memiliki referensi dalam file untuk System.Web.MVC dan BUKAN System.Web. Terima kasih atas bantuan Anda.
John Peters
7

Saya tahu ini adalah subjek yang lebih tua, namun mengejek aplikasi MVC untuk pengujian unit adalah sesuatu yang kami lakukan secara teratur.

Saya hanya ingin menambahkan pengalaman saya Mengejek aplikasi MVC 3 menggunakan Moq 4 setelah memutakhirkan ke Visual Studio 2013. Tidak ada tes unit yang bekerja dalam mode debug dan HttpContext menunjukkan "tidak bisa mengevaluasi ekspresi" ketika mencoba mengintip variabel .

Ternyata studio visual 2013 memiliki masalah mengevaluasi beberapa objek. Agar debugging aplikasi web yang diolok-olok berfungsi lagi, saya harus memeriksa "Gunakan Mode Kompatibilitas Terkelola" di Alat => Opsi => Debugging => Pengaturan umum.

Saya biasanya melakukan sesuatu seperti ini:

public static class FakeHttpContext
{
    public static void SetFakeContext(this Controller controller)
    {

        var httpContext = MakeFakeContext();
        ControllerContext context =
        new ControllerContext(
        new RequestContext(httpContext,
        new RouteData()), controller);
        controller.ControllerContext = context;
    }


    private static HttpContextBase MakeFakeContext()
    {
        var context = new Mock<HttpContextBase>();
        var request = new Mock<HttpRequestBase>();
        var response = new Mock<HttpResponseBase>();
        var session = new Mock<HttpSessionStateBase>();
        var server = new Mock<HttpServerUtilityBase>();
        var user = new Mock<IPrincipal>();
        var identity = new Mock<IIdentity>();

        context.Setup(c=> c.Request).Returns(request.Object);
        context.Setup(c=> c.Response).Returns(response.Object);
        context.Setup(c=> c.Session).Returns(session.Object);
        context.Setup(c=> c.Server).Returns(server.Object);
        context.Setup(c=> c.User).Returns(user.Object);
        user.Setup(c=> c.Identity).Returns(identity.Object);
        identity.Setup(i => i.IsAuthenticated).Returns(true);
        identity.Setup(i => i.Name).Returns("admin");

        return context.Object;
    }


}

Dan memulai konteks seperti ini

FakeHttpContext.SetFakeContext(moController);

Dan memanggil Metode di controller lurus ke depan

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);
agaton
sumber
Apakah ada alasan bagus untuk mengaturnya dengan jawaban yang diterima? Dari kelelawar ini tampaknya lebih rumit dan tampaknya tidak menawarkan manfaat tambahan.
kilkfoe
1
Ini menawarkan metode mengejek yang lebih detail / modular
Vincent Buscarello
4

Jika aplikasi pihak ketiga Anda mengarahkan ulang secara internal, maka lebih baik mengejek HttpContext dengan cara di bawah ini:

HttpWorkerRequest initWorkerRequest = new SimpleWorkerRequest("","","","",new StringWriter(CultureInfo.InvariantCulture));
System.Web.HttpContext.Current = new HttpContext(initWorkerRequest);
System.Web.HttpContext.Current.Request.Browser = new HttpBrowserCapabilities();
System.Web.HttpContext.Current.Request.Browser.Capabilities = new Dictionary<string, string> { { "requiresPostRedirectionHandling", "false" } };
Divang
sumber