How to use DI (Wirebox) in Unit Test

Hello Guys.

I am new to coldbox and I’m trying to test my SERVICES for unit Testing. Is there any way that I could mock wirebox. Correct me if I use wrong method.

User Model

component accessors="true"{

// Properties
	property name="id" type="string";
	property name="firstname" type="string";
	property name="lastname" type="string";
	property name="email" type="string";

user function init(){
		return this;
	}

–USER SERVICE—

property name="wirebox" 			inject="wirebox";
property name="populator" 			inject="wirebox:populator";
User function new(){
		return wirebox.getInstance("user");
	}
function getAll(){
	> users = queryExecute("SELECT * FROM users",{},
		{returnType="array"}).map(function(user){
		return populator.populateFromStruct(new(),user);
	});
}

For my USERSERVICETEST

 describe( "User Suite", function(){
 
 			it("Can view all users ", function(){
 
 				var user = createEmptyMock("models.user");
 
 
 				var qry = querySim("id,firstname,lastname,email
 				1 | luis | majano| test@gmail.com
 				2 | joe | louis |test1@gmail.com
 				3 | bob | lainez| test2@gmail.com");
 
 				user.$("getAll",qry);
 
 				var data = model.getAll();
 
 				expect(data).toBe(qry);
 				
 			});
 
 		});

When I run this test my injected dependency such as wirebox populator and function new doesn’t exist. my user.$(“getAll”,qry); doesn’t an array of objects.

Hello, Fuzzy!

There are two issues here, and I’m going to answer the easy one first:

Autowiring Test Models via Wirebox

You don’t need to mock Wirebox - in this case, you’d much prefer that Wirebox “do its thing” and inject the necessary services so you can test your actual, running code.

Try to “autowire”, or “inject stuff” into your test object (the model under test) to simulate a normal, running application:

getWirebox().autowire( model )

Mocking Too Many Things

Personally, I think your test is trying to mock too many things. This creates several problems:

  1. Your test is “fragile” - i.e., you’ll need to constantly update your query mock to match the User.getAll query.
  2. Your test is too omniscient - it tries to “know all” about the User Service and what methods the UserService calls. What if UserService starts calling User.getTop10() instead of User.getAll()? Your test will break again.
  3. You are not testing the full model, end-to-end, and thus you may be missing bugs because you are mocking pieces. Whenever possible, try to create tests that match reality.

Also note that you didn’t even mock the user.getAll() method properly, because you didn’t tell your UserService about your mock user object. This is why the wirebox populator and such don’t exist - because the model, i.e. UserService, was still calling the User object not your mock. (Unless you didn’t show us the full code?)

Instead of mocking the User object, I would suggest creating and “seeding” a test database and running your tests against a working database. You would need a basic table creation script as well as some dummy data (we’d call them “fixtures”) to exist in the test database. You can use CFMigrations or just a .sql script file that you run against MySQL or similar.

Good luck!

1 Like

I should be noted this appears to be a continuation of this original Stack Overflow question here:

How to use DI (Wirebox) in Unit Test

As I said on SO, you don’t. A unit test is there only to test a single function in your code. After all, you don’t want to be testing WireBox in your test. This is why everything needs to be mocked. Wirebox must be mocked. The populator must be mocked. Any helper methods get mocked-- you get the picture. I showed you on SO how to get started creating a stub for the populator and you’d keep doing the same thing for WireBox and any other external calls.

I need to clarify something I mistakenly said on SO however. I said

I don’t think your queryMap() is returning a struct like you think it is

but what I didn’t see was that your queryExecute() had

{returnType="array"}

which makes more sense now Your users variable does not contain a query object, but instead an array of structs. This doesn’t really change your approach, I just wanted to clarify that I had missed that.

Now, that said, on SO your test had this

expect( event.getPrivateValue( "users") ).toBeStruct();

which still wasn’t correct since the result of the arraymap() would still be an array so you’d get an array of structs, not a struct back. Now your test has this

expect(data).toBe(qry);

but this still isn’t correct as data will be an array of structs and qry will be a query object. I think you’re on the right track, but you’ve got a bunch of wires crossed"

  • querySim() is great, but it produces a query object, not an array of structs. Therefore it’s not suitable to mock the data structure returned from queryExecute when you’re using returntype="array". I’d just recommend creating an arrary of structs yourself.
  • Sadly, the queryExecute() itself isn’t actually mock-able! That’s because it’s a first class CFML function right there in your getAll() method so there’s no way for you to swap it out with a mock. For this to work, you’d need either a DAO layer or at least another function responsible ONLY for running the query. Then you’d be able to mock the DAO or the query-running method.
  • You’re creating an empty mock of models.user which is fine if that’s what you are going to have your mocked wirebox.getInstance() return, but you seem to then be mocking the getAll() method on your user object, when that method was supposed to exist on your userService object!

I think you’re close, but you’ve got to straighten out what you’re mocking, where, and how. If all this seems tedious, it’s because it is. Unit testing is a pain because of how tedious it is and how much setup it requires. Which is why Michael and I both pointed out integration testing where you work with fully built objects all wired up by WireBox. You can decide if you want integration tests hitting the DB or if you still want to mock a DAO layer, but they can be effective at a “higher level” in the app without all the setup.

Why did you do this? There was nothing wrong with your use of the populator. In fact, now that you’ve made the method have nothing but a query there is really no use to test it at all!

mockUsers= model.$(“getAll”,qry);

No, you don’t want to mock the actual getAll() method-- that totally defeats the purpose of the test. now you’re not actually running any code at all when you run your test! Now, for what it’s worth, your test does appear to be properly creating a mock method and calling it so that’s good that you’re getting the hang of TestBox and MockBox, but the test itself serves no purpose as it’s currently written :slight_smile:

The nature of unit tests is to test your business logic. The higher the cyclomatic complexity of the function, the more tests you need. (That’s a fancy word for how many code paths there are, i.e. if statements, loops, switches, etc) So a function that only does something like this

function getFoo() {
  return variables.foo;
}

or this

function runQuery() {
  return queryExecute();
}

really is not deserving of a test at all unless you simply want to ensure the code compiles! That’s because these functions aren’t really doing anything.

What I recommend is to do the bulk of your testing as high level integration tests that hit ColdBox events/routes and save your unit tests for specific method that have some good business logic in them, such as a function that takes several inputs, churns through some business logic, and returns pricing based on the inputs and the business rules. That would be a great unit test.