with verifiable unit tests setup property objetos mock and unit-testing moq

unit testing - verifiable - Al burlarse de una clase con Moq, ¿cómo puedo usar CallBase para solo métodos específicos?



verify moq (3)

Realmente aprecio el comportamiento de burla Loose de Moq que devuelve valores predeterminados cuando no se establecen expectativas. Es conveniente y me ahorra código, y también actúa como una medida de seguridad: las dependencias no se llamarán involuntariamente durante la prueba de la unidad (siempre que sean virtuales).

Sin embargo, estoy confundido acerca de cómo mantener estos beneficios cuando el método bajo prueba pasa a ser virtual.
En este caso , quiero llamar al código real para ese único método, mientras sigo teniendo el resto de la clase sin burla.

Todo lo que encontré en mi búsqueda es que podría establecer mock.CallBase = true para garantizar que se llame al método. Sin embargo, eso afecta a toda la clase. No quiero hacer eso porque me pone en un dilema sobre todas las otras propiedades y métodos de la clase que ocultan las dependencias de llamadas: si CallBase es verdadero, entonces tengo que

  1. Los apéndices de instalación de todas las propiedades y métodos que ocultan las dependencias: aunque mi prueba no crea que deba preocuparse por esas dependencias, o
  2. Espero que no me olvide de configurar ningún stubs (y que no se agreguen nuevas dependencias al código en el futuro) - Pruebas de unidad de riesgo que golpean una dependencia real.

Lo que creo que quiero es algo así como:
mock.Setup(m => m.VirtualMethod()).CallBase();
de modo que cuando llamo a mock.Object.VirtualMethod() , Moq llama a la implementación real ...

P: Con Moq, ¿hay alguna manera de probar un método virtual, cuando me burlé de la clase para poner algunas pocas dependencias? Es decir, sin recurrir a CallBase = true y tener que colgar todas las dependencias?

Código de ejemplo para ilustrar
(usa MSTest, InternalsVisibleTo DynamicProxyGenAssembly2)

En el siguiente ejemplo, TestNonVirtualMethod pasa, pero TestVirtualMethod falla - devuelve nulo.

public class Foo { public string NonVirtualMethod() { return GetDependencyA(); } public virtual string VirtualMethod() { return GetDependencyA();} internal virtual string GetDependencyA() { return "! Hit REAL Dependency A !"; } // [... Possibly many other dependencies ...] internal virtual string GetDependencyN() { return "! Hit REAL Dependency N !"; } } [TestClass] public class UnitTest1 { [TestMethod] public void TestNonVirtualMethod() { var mockFoo = new Mock<Foo>(); mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); string result = mockFoo.Object.NonVirtualMethod(); Assert.AreEqual(expectedResultString, result); } [TestMethod] public void TestVirtualMethod() // Fails { var mockFoo = new Mock<Foo>(); mockFoo.Setup(m => m.GetDependencyA()).Returns(expectedResultString); // (I don''t want to setup GetDependencyB ... GetDependencyN here) string result = mockFoo.Object.VirtualMethod(); Assert.AreEqual(expectedResultString, result); } string expectedResultString = "Hit mock dependency A - OK"; }


Como nadie ha respondido esta pregunta durante años y creo que merece una respuesta, me centraré en la pregunta de mayor nivel que me hizo: cómo mantener estos beneficios cuando el método bajo prueba es virtual.

Respuesta rápida: no puedes hacerlo con Moq, o al menos no directamente. Pero puedes hacerlo.

Digamos que tiene dos aspectos de comportamiento, donde el aspecto A es virtual y el aspecto B no. Eso más o menos refleja lo que tienes en tu clase. B puede usar otros métodos o no; tu decides.

Por el momento, su clase Foo está haciendo dos cosas, ambas A y B. Puedo decir que son responsabilidades separadas solo porque quiere burlarse de A y probar B por sí mismo.

En lugar de tratar de burlarse del método virtual sin burlarse de nada más, puede:

  • mover el comportamiento A a una clase separada
  • la dependencia inyecta la nueva clase con A a Foo través del constructor de Foo
  • invocar esa clase de B.

Ahora puede simular A, y aún llamar al código real para B..N sin invocar realmente el A. real. Puede mantener A virtual o acceder a él a través de una interfaz y simular eso. Esto está en línea con el Principio de Responsabilidad Individual, también.

Puedes poner en cascada los constructores con Foo , hacer que el constructor Foo() llame al constructor Foo(new A()) , por lo que ni siquiera necesitas un marco de inyección de dependencias para hacer esto.

¡Espero que esto ayude!


Creo que la respuesta de Lunivore fue correcta en el momento en que fue escrita.

En las versiones más nuevas de Moq (creo que desde la versión 4.1 de 2013), es posible hacer lo que quiera con la sintaxis exacta que proponga. Es decir:

mock.Setup(m => m.VirtualMethod()).CallBase();

Esto configura el simulacro libre para llamar a la implementación base de VirtualMethod lugar de simplemente devolver el default(WhatEver) , pero solo para este miembro ( VirtualMethod ).

Como las notas de BornToCode del usuario en los comentarios, esto no funcionará si el método tiene el tipo de retorno void . Cuando VirtualMethod no es nulo, la llamada de Setup da un Moq.Language.Flow.ISetup<TMock, TResult> que hereda el método CallBase() de Moq.Language.Flow.IReturns<TMock, TResult> . Pero cuando el método es nulo, obtenemos un Moq.Language.Flow.ISetup<TMock> que carece del método CallBase() deseado.


Hay una manera de llamar al método real y recuperar una llamada cuando el método es void , pero es realmente hacky. Tienes que devolver la llamada, llámalo explícitamente y engaña a Moq para que llame al real.

Por ejemplo, dada esta clase

public class MyInt { public bool CalledBlah { get; private set; } public virtual void Blah() { this.CalledBlah = true; } }

Puedes escribir tu prueba de esta manera:

[Test] public void Test_MyRealBlah() { Mock<MyInt> m = new Mock<MyInt>(); m.CallBase = true; bool calledBlah = false; m.When(() => !calledBlah) .Setup(i => i.Blah()) .Callback(() => { calledBlah = true; m.Object.Blah(); }) .Verifiable(); m.Object.Blah(); Assert.IsTrue(m.Object.CalledBlah); m.VerifyAll(); }

El aspecto clave es que rastreas si se ha llamado a la versión falsa, y luego configuras el simulacro para que no llame al falso si ya se ha llamado.

Todavía puedes hacer algo similar si tomas argumentos y el valor importa:

public class MyInt { public List<int> CalledBlahArgs { get; private set; } public MyInt() { this.CalledBlahArgs = new List<int>(); } public virtual void Blah(int a) { this.CalledBlahArgs.Add(a); } } [Test] public void Test_UpdateQueuedGroups_testmockcallback() { Mock<MyInt> m = new Mock<MyInt>(); m.CallBase = true; List<int> fakeBlahArgs = new List<int>(); m.Setup(i => i.Blah(It.Is<int>(a => !fakeBlahArgs.Contains(a)))) .Callback<int>((a) => { fakeBlahArgs.Add(a); m.Object.Blah(a); }) .Verifiable(); m.Object.Blah(1); Assert.AreEqual(1, m.Object.CalledBlahArgs.Single()); m.Verify(b => b.Blah(It.Is<int>(id => 1 == id))); }