Showing posts with label unit testing. Show all posts
Showing posts with label unit testing. Show all posts

Wednesday, May 20, 2009

Testing private methods using Reflection

- What about testing private methods ?
- Hum... You can't because they're private dummy.
- And what about using reflection to make them accessible ?

Because we don't have access to private methods, we generally don't test them. This can be a weakness in your testing strategy. Private methods are usually a very sensible part of your code. I've have seen a lot of developers modifying the visibility of their code from private to protected. That's a bad practice. Don't change the visibility of your code for testing purposes.

A solution consists of using reflection to make these private methods accessible for testing.

I really don't like to use reflexion in my application code because if someone is doing some refactoring like renaming a method and forget to update the reflexive code part, you will get a very bad runtime exception. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it.

Test code is not application code. Your tests does not go into production, that's why I'm not afraid of using reflection in my test classes.

Here's an example :

MyClass.java

public class MyClass {

private String myPrivateMethod(Long id) {
//Do something private
return "SomeString_" + id;
}
}

MyClassTest.java

import java.lang.reflect.Method;

import static org.junit.Assert.*;
import org.junit.Test;

public class MyClassTest {

private MyClass underTest;

@Test
public void testMyPrivateMethod() throws Exception {

underTest = new MyClass();

Class[] parameterTypes = new Class[1];
parameterTypes[0] = java.lang.Long.class;

Method m = underTest.getClass().getDeclaredMethod("myPrivateMethod", parameterTypes);
m.setAccessible(true);

Object[] parameters = new Object[1];
parameters[0] = 5569L;

String result = (String) m.invoke(underTest, parameters);

//Do your assertions
assertNotNull(result);
}
}

Sunday, May 10, 2009

Good unit testing with JMock 2

Sometimes it can be difficult to unit test objects like services which can return multiple errors. A good unit testing strategy is to test every part of your code. JMock can help you do that by mocking your objects and code what they will return. You don't need to create your mock objects anymore.

Here's an example.

Account.java :
package com.jmock.vo;

public class Account {

private String id;
private String name;
private boolean activated;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
}

AccountServicesImpl.java is the class under test.

AccountServicesImpl.java :
package com.jmock.services.impl;

import org.springframework.beans.factory.annotation.Autowired;

import com.jmock.dao.AccountDAO;
import com.jmock.general.ErrorConst;
import com.jmock.services.exception.AccountServicesException;
import com.jmock.vo.Account;

public class AccountServicesImpl {

@Autowired
private AccountDAO accountDAO; //Spring injected

public Account getAccount(String id) throws AccountServicesException {
Account account = accountDAO.selectAccount(id);

if(account == null)
throw new AccountServicesException(ErrorConst.ERROR_ACCOUNT_UNKNOWN);

if(!account.isActivated())
throw new AccountServicesException(ErrorConst.ERROR_ACCOUNT_INACTIVE);

return account;
}
}

As you can see, there is 3 scenarios to implement :
- The normal test case
- The account unknown test case
- The account inactive test case

And here's the test class.

AccountServicesTest.java :

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;

import com.jmock.dao.AccountDAO;
import com.jmock.general.ErrorConst;
import com.jmock.services.exception.AccountServicesException;
import com.jmock.services.impl.AccountServicesImpl;
import com.jmock.vo.Account;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class AccountServicesTest {

//Mock context
private Mockery context;

private AccountDAO accountDAO;

private AccountServicesImpl underTest;

@Before
public void before() throws Exception {
context = new JUnit4Mockery();
accountDAO = context.mock(AccountDAO.class);

underTest = new AccountServicesImpl();

//We use reflection to access the private field
Field fieldCurrencyServices = underTest.getClass().getDeclaredField("accountDAO");
fieldCurrencyServices.setAccessible(true);
fieldCurrencyServices.set(underTest, accountDAO);
}

@Test
public void testGetAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(true);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(account));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
ex.printStackTrace();
fail("No exception expected");
}
}

@Test
public void testGetUnknownAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(true);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(null));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
assertEquals(ErrorConst.ERROR_ACCOUNT_UNKNOWN, ex.getErrorId());
}
}

@Test
public void testGetInactiveAccount() {
final Account account = new Account();
account.setId("124110002055");
account.setActivated(false);

context.checking(new Expectations() {{
oneOf (accountDAO).selectAccount(with(account.getId()));
will(returnValue(account));
}});

try {
underTest.getAccount(account.getId());
} catch (AccountServicesException ex) {
assertEquals(ErrorConst.ERROR_ACCOUNT_INACTIVE, ex.getErrorId());
}
}
}

Here's are the dependencies you need to add to your pom.xml
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-junit4</artifactId>
<version>2.5.1</version>
</dependency>