views:

126

answers:

1

I'm a newbie in Unit Test with Mock Object. I use EasyMock. I try to understand this example:

import java.io.IOException;

public interface ExchangeRate {

    double getRate(String inputCurrency, String outputCurrency) throws IOException;

}

import java.io.IOException;


public class Currency {

    private String units;
    private long amount;
    private int cents;


    public Currency(double amount, String code) {
        this.units = code;
        setAmount(amount);
    }

    private void setAmount(double amount) {
        this.amount = new Double(amount).longValue();
        this.cents = (int) ((amount * 100.0) % 100);
    }

    public Currency toEuros(ExchangeRate converter) {
        if ("EUR".equals(units)) return this;
        else {
            double input = amount + cents/100.0;
            double rate;
            try {
                rate = converter.getRate(units, "EUR");
                double output = input * rate;
                return new Currency(output, "EUR");
            } catch (IOException ex) {
                return null;
            }
        }
    }

    public boolean equals(Object o) {
        if (o instanceof Currency) {
            Currency other = (Currency) o;
            return this.units.equals(other.units)
                    && this.amount == other.amount
                    && this.cents == other.cents;
        }
        return false;
    }

    public String toString() {
        return amount + "." + Math.abs(cents) + " " + units;
    }

}

import junit.framework.TestCase;
import org.easymock.EasyMock;
import java.io.IOException;

public class CurrencyTest extends TestCase {

    public void testToEuros() throws IOException {
        Currency testObject = new Currency(2.50, "USD");
        Currency expected = new Currency(3.75, "EUR");
        ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
        EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
        EasyMock.replay(mock);
        Currency actual = testObject.toEuros(mock);
        assertEquals(expected, actual);
    }

}

So, i wonder how to Currency use ExchangeRate in toEuros(..) method.

rate = converter.getRate(units, "EUR");

The behavior of getRate(..) method is not specified because ExchangeRate is an interface.

/********************************************************************************/

So I try do myself example. Following is my codes:

/**
 *Interface to access data
 */
public interface Dao {
    public boolean getEntityById(int id) throws SQLException;
}

/**
 *Business class do something in business layer
 */
public class Bussiness {
    private Dao dao;

    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public boolean doSomeThing(int id) throws SQLException {
        if(dao.getEntityById(id)) {
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) throws SQLException {
        Bussiness b = new Bussiness();
        b.doSomeThing(3);
    }
}


package tunl;

import java.sql.SQLException;

import org.easymock.EasyMock;
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

    /**
     * This is my unit Test
     */
    @Test
    public class MyUnitTest {
        private Bussiness bussiness;
        private Dao mock;

        @BeforeTest
        public void setUp() {
            bussiness = new Bussiness();
            mock = EasyMock.createMock(Dao.class);// interface not class
            bussiness.setDao(mock);
        }

        public void testDoSomeThing() throws SQLException {
            EasyMock.expect(mock.getEntityById(3)).andReturn(true);
            EasyMock.replay(mock);
            Assert.assertTrue(bussiness.doSomeThing(3));
        }
    }

So, The unit Tess run correctly

But when i want to run main method in Business Object:

public static void main(String[] args) throws SQLException {
            Bussiness b = new Bussiness();
            b.doSomeThing(3);
}

I have to add the constructor for Business.

public Bussiness() {
     dao = new DaoImpl();
}

So, my business class is:

package tunl;

import java.sql.SQLException;

public class Bussiness {
    private Dao dao;

    public Bussiness() {
        dao = new DaoImpl();
    }
    public Dao getDao() {
        return dao;
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public boolean doSomeThing(int id) throws SQLException {
        if(dao.getEntityById(id)) {
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) throws SQLException {
        Bussiness b = new Bussiness();
        b.doSomeThing(3);
    }
}

Also I have to implement Dao interface:

package tunl;

import java.sql.SQLException;

public class DaoImpl implements Dao {

    @Override
    public boolean getEntityById(int id) throws SQLException {
        if(id == 3) {
            System.out.println("System input 3 ");
            return true;
        }
        System.out.println("You have to input  3 ");
        return false;
    }

}

In design, you always create interface for all of the classes which will be tested (like DaoImpl) !!! So is it correct?

+1  A: 

EasyMock creates a mock object based on the interface. The mock object implements all the methods of the interface and for those methods you specify (e.g. with expect), it "replays" the specified behaviour when they are called.

When a mock object is created, it is in recording mode. The line

EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);

specifies that when mock.getRate is called with the given parameters, it shall return 1.5 . Then the object is put into replay mode with the call

EasyMock.replay(mock);

All this is explained in more details in the documentation.

Update: to your comment - Currency is passed an instance of ExchangeRate here:

public Currency toEuros(ExchangeRate converter) { ... }

All it cares is that it gets an object implementing that interface, so that

rate = converter.getRate(units, "EUR");

can be called. The test method, then, passes the mock object it created to the currency object:

Currency actual = testObject.toEuros(mock);

Hope this helps; if not, maybe you could read some introductory text on OOP, interfaces and inheritance to get a better understanding.

In the code example in your answer, the Dao object should be passed to Bussiness rather than created internally, since the latter effectively prevents unit testing.

public static void main(String[] args) throws SQLException {
        Bussiness b = new Bussiness();
        b.setDao(new DaoImpl());
        b.doSomeThing(3);
}

You could also add a parameterized constructor to Bussiness to make the initialization in one step instead of two.

Péter Török
Thanks mr Péter Török,The interface's method are specified in CurrencyTest by EasyMock. But I still don't understand that How to Currency uses Interface's methods(not CurrencyTest). Because you always have to use interface in EasyMock so how to business classes response.
@tunl, see my update.
Péter Török
Now, I understood. My weakness is that I don't know how to pass DaoImpl to Business. And the Mock always go together with Interface.Thank you again and sorry about my mistakes.