miércoles, 18 de junio de 2008

Restricciones sobre parámetros objetos con Rhino Mocks

Restricciones sobre parámetros objetos con Rhino Mocks

Entre las restricciones que nombré en el post anterior se encuentra Property, la misma posee algunos métodos estáticos que nos permiten colocar restricciones a los parámetros de nuestros métodos "moquedos" en caso que sean tipos de referencia (objetos) y quisieramos verificar una condición para una de sus propiedades. Por ejemplo que quisieramos que la propiedad "nombre" del objeto "persona" que es parámetro del método "GuardarPersona" en una clase "moqueada" sea exáctamente "Leonardo" o contenga el texto "Lalo Landa", o incluso que contenga otras restriccions formadas por las otras clases para restricción como Is, Text, etc. vamos diréctamente al código para ver su utilización:

[TestClass]
public class RhinoMocksPropertyConstraint
{
    /// <summary>
    /// Property.Value("nombrePropiedad", "valor")
    /// quiere decir que el valor de la propiedad nombrePropiedad debe
    /// ser valor cuando se utiliza como parámetro de un método "moquedo"
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void PropertyValueConstraint()
    {
        MockRepository mocker = new MockRepository();

        IProcessor mockProcessor = mocker.CreateMock<IProcessor>();

        using (mocker.Record())
        {
            //le decimos al Rhino que la llamada al método debe hacerce
            //con un objeto ClaseParametro cuya propiedad ValorTexto
            //debe tener valor "HOLA"
            Expect.Call(
                mockProcessor.TestMethod(null))
                .IgnoreArguments()
                .Return("")
                .Constraints(Property.Value("ValorTexto", "HOLA"))
                .Repeat.Any();
        }
        //Este assert es correcto, el valor de la propiedad ValorTexto es HOLA
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0, "HOLA")).Length == 0);

        //Lanza una ExpectationViolationExpcetion porque el valor de la propiedad
        //ValorTexto no es "HOLA"
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0, "CHAU")).Length == 0);

        mocker.VerifyAll();
    }
    
    /// <summary>
    /// PropertyValueConstraint permite poner cualquier tipo de constraint
    /// en una propiedad de un objeto que es parámetro de un método "moquedo"
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void PropertyConstraintValueConstraint()
    {
        MockRepository mocker = new MockRepository();

        IProcessor mockProcessor = mocker.CreateMock<IProcessor>();

        using (mocker.Record())
        {
            //le decimos al Rhino que la llamada al método debe hacerce
            //con un objeto ClaseParametro cuya propiedad ValorTexto
            //debe tener contener en su valor ek texto "HOLA"
            Expect.Call(
                mockProcessor.TestMethod(null))
                .IgnoreArguments()
                .Return("")
                .Constraints(Property.ValueConstraint("ValorTexto", Text.Contains("HOLA")))
                .Repeat.Any();
        }
        //Este assert es correcto, el valor de la propiedad ValorTexto contiene la palabra HOLA
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0, "HOLA MUNDO")).Length == 0);

        //Lanza una ExpectationViolationExpcetion porque el valor de la propiedad
        //ValorTexto no contiene "HOLA"
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0, "CHAU")).Length == 0);

        mocker.VerifyAll();
    }

    /// <summary>
    /// IsNull, el objeto parámetro del método "moquedo" deber ser null
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void PropertyConstraintIsNullConstraint()
    {
        MockRepository mocker = new MockRepository();

        IProcessor mockProcessor = mocker.CreateMock<IProcessor>();

        using (mocker.Record())
        {
            //le decimos al Rhino que la llamada al método debe hacerce
            //con un parámetro null
            Expect.Call(
                mockProcessor.TestMethod(null))
                .IgnoreArguments()
                .Return("")
                .Constraints(Property.IsNull("ValorTexto"))
                .Repeat.Any();
        }
        //Este assert es correcto, el parámetro es null
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0,null)).Length==0);

        //Lanza una ExpectationViolationExpcetion porque el valor de la propiedad
        //no es null
        Assert.IsTrue(mockProcessor.TestMethod(new ClaseParametro(0, "")).Length == 0);

        mocker.VerifyAll();
    }
}



public interface IProcessor
{
    string TestMethod(ClaseParametro parametro);
}

public class ClaseParametro
{
    private int _valorEntero;
    private string _valorTexto;

    public ClaseParametro(int valorEntero, string valorTexto)
    {
        this.ValorEntero = valorEntero;
        this.ValorTexto = valorTexto;
    }

    public string ValorTexto
    {
        get { return _valorTexto; }
        set { _valorTexto = value; }
    }


    public int ValorEntero
    {
        get { return _valorEntero; }
        set { _valorEntero = value; }
    }

}

Simple, hay algunos métodos más en esta clase pero creo que no vale la pena explicarlos ya que son muy intuítivos, saludos.

lunes, 16 de junio de 2008

Restricciones con Rhino Mock 3.4 o Constraints en Rhino Mocks, o cómo limitar los valores de los parámetros con mock objects

Rhino Mocks Constraints o restricciones en Rhino Mocks 3.4
Otra de las características interesantes de un framework de mock es la posiblidad de poner restricciones a los valores que pueden recibir los parámetros de un objeto que estamos "moqueando", esto es, que nuestra Expectation permite definir que el método sólo pueda ser llamado con ciertos valores en sus parámetros, por ejemplo que un parámetros sea exáctamente 2 o mayor de 5 y mucho más.

Tipos de restricciones en Rhino Mocks

Básicamente tenemos cuatro clases con métodos estáticos para trabajar con restricciones:

Is : permite establecer restricciones del tipo "igual a" "mayor que"
Property: para trabajar con propiedades, por ejemplo propiedad tal debe tener tal valor
List: para las listas, se pueden poner restricciones a la cantidad de elementos, etc.
Text: para trabajar con texto, por ejemplo poner una validación basada en una expresión regular

En caso de no cumplir con una restricción para un parámetro Rhino nos lanza una bonita ExpectationViolationException, veámos un ejemplo:

[TestClass]
public class UnitTest1
{

    MockRepository _mocker;

    public MockRepository Mocker
    {
        get
        {
            if (_mocker == null)
                _mocker = new MockRepository();
            return _mocker;
        }
    }

    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void ViolacionDeConstraint()
    {
        IProcessor mockProcessor = Mocker.CreateMock<IProcessor>();
        
        using (Mocker.Record())
        {
            //grabamos un comportamiento que dice que se espera
            //una llamada el método Test en la cual el argumento
            //debe ser mayor a 3
            Expect.Call(mockProcessor.Test(0)).
                Return(true).
                Constraints(Is.GreaterThan(3));
        }

        using (Mocker.Playback())
        {
            //funciona porque devuelve true en caso de llamar al métod
            //con un parámetro mayor que 3
            Assert.IsTrue(mockProcessor.Test(4));
            //Lanza una ExpectationViolationException porque el parámetro
            //viola el constraint
            Assert.IsTrue(mockProcessor.Test(2));            
        }
    }
}

public interface IProcessor
{
    bool Test(int value);
}

Para terminar, los remito a la documentación para conocer todas las restricciones, saludos.

lunes, 9 de junio de 2008

"Moqueando" clases abstractas con Rhino Mocks

Una interesante funcionalidad de RhinoMocks es la posiblidad de crear instancias de clases abstractas y utilizar sus método concretos sin más que la ayuda de los PartialMocks, incluso podemos dar comportamiento a sus métodos abstractos, con los cual es posible probar una clase abstracta en su totalidad sin tener una implementación concreta, vamos a ver en el famoso código autodescriptivo.

[TestClass]
public class UnitTest1
{
    MockRepository _repository;
    public MockRepository Mocker
    {
        get
        {
            if (_repository == null)
                _repository = new MockRepository();
            return _repository;
        }
    }
    /// <summary>
    /// Los PartialMocks nos permiten crear instancias de clases abstractas
    /// para poder probar los métodos concretos con su implementación real
    /// sin necesidad de heredar en una clase concreta
    /// </summary>
    [TestMethod]
    public void PartialMockTest()
    {
        //se pueden crear instancias de clases abstractas para utlizar los
        //métodos concretos
        AbstractProcessor processor = Mocker.PartialMock<AbstractProcessor>();

        //y funciona :-)
        Assert.AreEqual<string>("Hola Leonardo", processor.Saludar("Leonardo"));
    }

    /// <summary>
    /// No podemos modificar el comportamiento de método concreto
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void PartialMockConfigurarComportamientoMetodoConcreto()
    {
        AbstractProcessor processor = Mocker.PartialMock<AbstractProcessor>();

        using (Mocker.Record())
            Expect.Call(processor.Saludar("")).Return("Método modificado").IgnoreArguments();

        //utilizo la sintáxis de Record-Playback en lugar de ReplayAll-VerifyAll
        using (Mocker.Playback())
            Assert.AreEqual<string>("Método modificado", processor.MetodoUno());
    }

    /// <summary>
    /// También podemos configurar el comportamiento
    /// de los métodos abstratos y concretos
    /// </summary>
    [TestMethod]
    public void PartialMockConfigurarComportamientoMetodoAbstracto()
    {
        //se pueden crear instancias de clases abstractas para utlizar los
        //métodos concretos
        AbstractProcessor processor = Mocker.PartialMock<AbstractProcessor>();

        using (Mocker.Record())
            Expect.Call(processor.MetodoUno()).Return("Metodo1 moqueado");

        //utilizo la sintáxis de Record-Playback en lugar de ReplayAll-VerifyAll
        using (Mocker.Playback())
            Assert.AreEqual<string>("Metodo1 moqueado", processor.MetodoUno());
    }
}

/// <summary>
/// Una clase abstracta no se puede instanciar directamente
/// </summary>
public abstract class AbstractProcessor
{
    //método abstracto, sin implementaci´n
    public abstract string MetodoUno();
    //método concreto, para usarlo es necesario heredar esta clase
    public string Saludar(string nombre)
    {
        return string.Format("Hola {0}", nombre);
    }
}

Interesante, utilizalo con sabiduría, saludos

domingo, 8 de junio de 2008

Diferentes tipos de Mock Objects con Rhino Mocks

Rhino Mocks nos permite crear diferentes tipos de Mock Objetcts según nuestras necesidades, en total son 3 Mocks, Dynamic Mocks y Stubs, simplemente vamos a ver un ejemplo autodescriptivo, para ver los tres tipos y sus aplicaciones.

[TestClass]
public class UnitTest1
{
    MockRepository _repository;

    MockRepository Mocker
    {
        get
        {
            if (_repository == null)
                _repository = new MockRepository();
            return _repository;
        }
    }

    /// <summary>
    /// Los mock dynamic no verifican la llamada a ningún tipo de métod
    /// sirven por ejemplo para configurar respuestas a métodos pero no verificar
    /// si son llamados
    /// </summary>
    [TestMethod]
    public void DynamicTest()
    {
        IDummy MockDummy = Mocker.DynamicMock<IDummy>();

        Expect.Call(MockDummy.DummyMethod("")).Return("hola");

        Mocker.ReplayAll();

        //se retorna el valor configurado
        Assert.AreEqual("hola",MockDummy.DummyMethod(""));

        //nada me impide llamar varias veces a un método a pesar de no encontrase
        //configurado para ello, sin embargo no retorna el valor configurado
        Assert.AreEqual(null, MockDummy.DummyMethod(""));

        Mocker.VerifyAll();
    }

    /// <summary>
    /// Un mock (Strict replay)
    /// verifica que se llamen a los métodos esperados y sólo a estos
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void MockLlamadaMetodoNoEsperado()
    {
        IDummy MockDummy = Mocker.CreateMock<IDummy>();
        
        Mocker.ReplayAll();

        //lanza una excepción de uso inválido porque se llama a un método que no se esperaba
        MockDummy.DummyMethod("");

        Mocker.VerifyAll();
    }
    /// <summary>
    /// Un mock (Strict replay)
    /// verifica que se llamen a los métodos esperados y sólo a estos
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(ExpectationViolationException))]
    public void MockNoSeLlamoEsperado()
    {
        IDummy MockDummy = Mocker.CreateMock<IDummy>();
        Expect.Call(MockDummy.DummyMethod("")).Return(null);

        Mocker.ReplayAll();

        //lanza una excepción de uso inválido porque no se llamó a un método esperado
        Mocker.VerifyAll();
    }

    /// <summary>
    /// Un stub no tiene comportamiento, es útil para crear objetos
    /// que necesitamos para hacer funcionar otros pero de los que 
    /// no esperamos ningún comportamiento
    /// </summary>
    [TestMethod]
    public void StubTest()
    {
        IUsuario usuario = Mocker.Stub<IUsuario>();

        Mocker.ReplayAll();

        //el stub se comporta como un objeto con propiedades
        usuario.Nombre = "leonardo";

        Assert.AreEqual<string>("leonardo", usuario.Nombre);

        Mocker.VerifyAll();
    }

    /// <summary>
    /// Un stub no tiene comportamiento, es útil para crear objetos
    /// que necesitamos para hacer funcionar otros pero de los que 
    /// no esperamos ningún comportamiento
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void StubNoAceptaComportamientoTest()
    {
        IUsuario usuario = Mocker.Stub<IUsuario>();
        //el stub no acepta comportamiento, lanza una excepción acá
        Expect.Call(usuario.ToString()).Return("usuario");

        Mocker.ReplayAll();

        usuario.Nombre = "leonardo";

        Assert.AreEqual<string>("leonardo", usuario.Nombre);

        Mocker.VerifyAll();
    }
}

Sencillo, hasta la próxima