开发者

How to mock HTTPSession/FlexSession with TestNG and some Mocking Framework

I'm developing a web application running on Tomcat 6, with Flex as Frontend. I'm testing my backend with TestNG. Currently, I'm trying to test the following method in my Java-Backend:

public class UserDAO extends AbstractDAO {
    (...)
    public UserPE login(String mail, String password) {
        UserPE dbuser = findUserByMail(mail); 
        if (dbuser == null || !dbuser.getPassword().equals(password))
            throw new RuntimeException("Invalid username and/or password");
        // Save logged in user
        FlexSession session = FlexContext.getFlexSession();
        session.setAttribute("user", dbuser);
        return dbuser;
    }
}    

The method needs access to the FlexContext which only exists when i run it on the Servlet container (don't bother if you don't know Flex, it's more a Java-Mocking question in general)开发者_如何学Go. Otherwise i get a Nullpointer exception when calling session.setAttribute(). Unfortunately, I cannot set the FlexContext from outside, which would make me able to set it from my tests. It's just obtained inside the method.

What would be the best way to test this method with a Mocking framework, without changing the method or the class which includes the method? And which framework would be the easiest for this use case (there are hardly other things i have to mock in my app, it's pretty simple)?

Sorry I could try out all of them for myself and see how i could get this to work, but i hope that i'll get a quickstart with some good advices!


Obvious one approach is to re-factor it in a way that lets you inject things like the FlexContext. However this is not always possible. Some time ago a team I was part of hit a situation where we had to mock out some internal class stuff that we didn't have access to (like your context). We ended up using an api called jmockit which allows you to effective mock individual methods, including static calls.

Using this technology we where able to get around a very messy server implementation and rather than having to deploy to live servers and black box test, we were able to unit test at a fine level by overriding the server technology that was effective hard coded.

The only recommendation I would make about using something like jmockit is to ensure that in your test code there is clear documentation and seperation of jomockit from you main mocking framework (easymock or mockito would be my recommendations). Otherwise you risk confusing developers about the various responsibilities of each part of the puzzle, which usually leads to poor quality tests or tests that don't work that well. Ideally, as we ended up doing, wrap the jmockit code into you testing fixtures so the developers don't even know about it. Dealing with 1 api is enough for most people.

Just for the hell of it, here's the code we used to fix testing for an IBM class. WE basically need to do two things,

  1. Have the ability to inject out own mocks to be returned by a method.
  2. Kill off a constructor that went looking for a running server.
  3. Do the above without having access to the source code.

Here's the code:

import java.util.HashMap;
import java.util.Map;

import mockit.Mock;
import mockit.MockClass;
import mockit.Mockit;

import com.ibm.ws.sca.internal.manager.impl.ServiceManagerImpl;

/**
 * This class makes use of JMockit to inject it's own version of the
 * locateService method into the IBM ServiceManager. It can then be used to
 * return mock objects instead of the concrete implementations.
 * <p>
 * This is done because the IBM implementation of SCA hard codes the static
 * methods which provide the component lookups and therefore there is no method
 * (including reflection) that developers can use to use mocks instead.
 * <p>
 * Note: we also override the constructor because the default implementations
 * also go after IBM setup which is not needed and will take a large amount of
 * time.
 * 
 * @see AbstractSCAUnitTest
 * 
 * @author Derek Clarkson
 * @version ${version}
 * 
 */

// We are going to inject code into the service manager.
@MockClass(realClass = ServiceManagerImpl.class)
public class ServiceManagerInterceptor {

    /**
     * How we access this interceptor's cache of objects.
     */
    public static final ServiceManagerInterceptor   INSTANCE                = new ServiceManagerInterceptor();

    /**
     * Local map to store the registered services.
 */
    private Map<String, Object>                         serviceRegistry = new HashMap<String, Object>();

    /**
     * Before runnin your test, make sure you call this method to start
     * intercepting the calls to the service manager.
     * 
     */
    public static void interceptServiceManagerCalls() {
        Mockit.setUpMocks(INSTANCE);
    }

    /**
     * Call to stop intercepting after your tests.
     */
    public static void restoreServiceManagerCalls() {
        Mockit.tearDownMocks();
    }

    /**
     * Mock default constructor to stop extensive initialisation. Note the $init
     * name which is a special JMockit name used to denote a constructor. Do not
     * remove this or your tests will slow down or even crash out.
     */
    @Mock
    public void $init() {
        // Do not remove!
    }

    /**
     * Clears all registered mocks from the registry.
     * 
     */
    public void clearRegistry() {
        this.serviceRegistry.clear();
    }

    /**
     * Override method which is injected into the ServiceManager class by
     * JMockit. It's job is to intercept the call to the serviceManager's
     * locateService() method and to return an object from our cache instead.
     * <p>
     * This is called from the code you are testing.
     * 
     * @param referenceName
     *           the reference name of the service you are requesting.
     * @return
     */
    @Mock
    public Object locateService(String referenceName) {
        return serviceRegistry.get(referenceName);
    }

    /**
     * Use this to store a reference to a service. usually this will be a
     * reference to a mock object of some sort.
     * 
     * @param referenceName
     *           the reference name you want the mocked service to be stored
     *           under. This should match the name used in the code being tested
     *           to request the service.
     * @param serviceImpl
     *           this is the mocked implementation of the service.
     */
    public void registerService(String referenceName, Object serviceImpl) {
        serviceRegistry.put(referenceName, serviceImpl);
    }

}

And here's the abstract class we used as a parent for tests.

public abstract class AbstractSCAUnitTest extends TestCase {

protected void setUp() throws Exception {
    super.setUp();
    ServiceManagerInterceptor.INSTANCE.clearRegistry();
    ServiceManagerInterceptor.interceptServiceManagerCalls();
}

protected void tearDown() throws Exception {
    ServiceManagerInterceptor.restoreServiceManagerCalls();
    super.tearDown();
}

}


Thanks to Derek Clarkson, I successfully mocked the FlexContext, making the login testable. Unfortunately, it's only possible with JUnit, as far as i see (tested all versions of TestNG with no success - the JMockit javaagent does not like TestNG, See this and this issues).

So this is how i'm doing it now:

public class MockTests {
    @MockClass(realClass = FlexContext.class)
    public static class MockFlexContext {
        @Mock
        public FlexSession getFlexSession() {
            System.out.println("I'm a Mock FlexContext.");
            return new FlexSession() {

                @Override
                public boolean isPushSupported() {
                    return false;
                }

                @Override
                public String getId() {
                    return null;
                }
            };
        }
    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        Mockit.setUpMocks(MockFlexContext.class);
        // Test user is registered here
        (...)
    }

    @Test
    public void testLoginUser() {
        UserDAO userDAO = new UserDAO();
        assertEquals(userDAO.getUserList().size(), 1);
        // no NPE here 
        userDAO.login("asdf@asdf.de", "asdfasdf");
    }
}

For further testing i now have to implement things like the session map myself. But thats okay as my app and my test cases are pretty simple.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜