Sunday, November 25, 2007

Possibly my last ColdSpring update this weekend...

At least I hope so, my fingers are getting tired! This last update is a little on the experimental side, but I have just committed AbstractAutowireTransactionalTests to cvs (CSP-89). Basically this works exactly like AbstractAutowireTests, except that it begins a transaction before each test and rolls it back after the test, unless you call setCommit() somewhere in your test. Like AbstractAutowireTests, you just supply the location of your config file, and setters for any beans you want to test. From there, go crazy inserting, updating and deleting things in your db, after the test everything is rolled back. This is so handy, we use it all the time (I know, I said that about AbstractAutowireTests, but what can I say? They are REALLY useful!).

Just a few things I need to point out. I took a very, very heavy handed approach to making this work. It's not going to be the fastest because Paul creates a new instance of your test class for each test method. At that time (in setUp()) I am doing some crazy proxying and method injection. I am actually creating a proxy, and then taking it apart, it's a bit on the nuts side. However, if Paul would make a tiny change to cfcUnit, I could clean a lot of that up. Anyway, I hope this one is even more useful than the last, and feel free to send bugs along to the coldspring list. Enjoy!

Saturday, November 24, 2007

Even More ColdSpring Updates!!

Ok, this is something my coworker Cliff Meyers has been trying to get me to write for ages. We use the Spring equivalent of this literally every day. Now, unit testing ColdSpring managed beans is not really all that hard, all you need to do is create a bean factory, load your config file, and call getBean() for anything you want to test. But you know what? That's too much work! Enter AbstractAutowireTests, which you will find in the new coldspring.cfcunit package (yes, I do intend to make more!). Here's how it works, extend this class instead of Paul's TestCase, override the getConfigLocations function, and that's it, everything else is handled for you! Oh yeah, this is an Autowired test case too, so just add setters for whatever bean you want to write tests against, VERY handy. Here's an example of a test case using this new component:



<cfcomponent extends="coldspring.cfcunit.AbstractAutowireTests">

    <cffunction name="getConfigLocations" access="public" returntype="string" output="false">
        <cfset var path = GetDirectoryFromPath(getMetaData(this).path) />
        <cfreturn path & "/testBeans.xml" />
    </cffunction>

    <cffunction name="onSetUp" access="public" returntype="void" output="false">
        <!--- this is where my setup would be... --->
        <cfset variables.sys = CreateObject('java','java.lang.System') />
        <cfset variables.sys.out.println("onSetUp executed!") />
    </cffunction>

    <cffunction name="onTearDown" access="public" returntype="void" output="false">
        <!--- this is where my teardown would be... --->
        <cfset variables.sys.out.println("onTearDown executed!") />
    </cffunction>

    <cffunction name="setStructBean" access="public" returntype="void" output="false">
        <cfargument name="structBean" type="coldspring.tests.structBean" required="true" />
        <cfset variables.structBean = arguments.structBean />
    </cffunction>

    <cffunction name="testNothing" access="public" returntype="void" output="false">
        <cfset variables.sys.out.println("some structBean data: " & variables.structBean.getData().intOne) />
        <cfset AssertNotNull(variables.structBean) />
    </cffunction>

</cfcomponent>


A few things to point out here. Right now setConfigLocations is not 100%, it only works with 1 config file, but I will have that done any time now. Also, paths are difficult with cfcUnit as far as location of your config file. Using GetDirectoryFromPath(getMetaData(this).path) ensures that I can refer to a path relative to the cfc being tested. And finally, I am using setUp and tearDown, which are called by cfcUnit's test runner to preform my own set up and tear down actions, so instead you should use onSetUp and onTearDown, which will be properly called by the AbstractAutowireTests. For me, this has been an incredible time saver at work, so I hope you enjoy and find this useful! Now if Paul Kenney answers my email I have even more to commit. Cough, cough...

More ColdSpring updates!

One thing I love about Spring is every once in a while you wish you had some special type of bean only to find that Spring already has it. Collection factory beans are some of these types of objects, which I actually use quite a bit at work. Let's say you have configured a component you use across many of your applications, so you put its configuration in a base file and include it in each application with ColdSpring's <include> tag. This is a great approach for reusability, but if you want to use arrays or structs to define application specific parameters to that component you're stuck. A lot of times we end up creating special 'config beans' so we can use the ref tag, but many times these are just wrappers over structs anyway. Why use that type of component at all, if you could just define a struct in your config file and reference it wherever you need it? Well, that's the point of the collection factories. Here's an example of these two new components at work:

First I have a ListFactoryBean instance, which you need to remember from spring semantics will return an Array.

<bean id="list.factory" class="coldspring.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
        <list>
            <value>one</value>
            <value>two</value>
            <value>three</value>
        </list>
    </property>
</bean>

And now here's a MapFactoryBean, which will return a Struct:

<bean id="map.factory" class="coldspring.beans.factory.config.MapFactoryBean">
    <property name="sourceMap">
        <map>
            <entry key="one">&tl;value>hello&tl;/value>
            <entry key="one"><value>world</value>
        </map>
    </property>
</bean>

Now I can use the ref tag for these maps and lists instead of directly defining them.

<bean id="testBean" class="coldspring.tests.testBean" lazy-init="false">
    <property name="myList">
        <ref bean="list.factory"/>
    </property>
    <property name="myMap">
        <ref bean="map.factory"/>
    </property>
</bean>

This may seem like a lot of extra xml, but it can really help with configuration reuse, or providing simpler mechanisms for overriding properties for unit tests. Of course if you find yourself wrapping structs or arrays with components just because you are working with ColdSpring, well, now you don't have to.

Friday, November 23, 2007

Long, long awaited ColdSpring updates!

Ok, it's been a while I admit, and I have been pretty damn quiet up in this here blog, however, I have a serious new goal. That goal is a ColdSpring 1.2 release, and it's going to be SOON!. So, to that end, I'm just going to post my activity so you know where we're at. 

I have committed 2 fixes that should make Mark Mandel and Barney B really happy. CSP-84 has been fixed, which is a problem where setting lazy-init to false does nothing at all. (Ooops it was never implemented, sorry...). You can now define the lazy initialization on a per bean basis with the lazy-init attribute, or set the global default by specifying default-lazy-init on the root bean element. CSP-83 was a semi bug that actually resulted in extraneous try/catching, but basically I had a bug that made it a little difficult to create custom factories. This is different than defining one bean as a factory for another bean with the factory-bean attribute. If you register a bean that extends coldspring.beans.factory.FactoryBean, that bean will automatically have getObject() called on it when you call getBean(). This can really come in handy for some advanced configuration. I know Mark Mandel was working with it, but ran into some bugs. But it is now fixed, happy day!

OK, that's it for right now, there is A LOT more to come...