π Textbook mocking
OOP and polymorphism make it easy to swap components as long as they preserve the interface
DI makes it even easier, by decoupling resource usage and creation
– some solutions can even hotswap
The Wikipedia definition, 2 slides ago, explicitly mentions Object-oriented programming. That's because
OOP and polymorphism make it easy to swap components that share a common interface without changes to
the underlying code – and that makes mocking easy.
In addition, Dependency Injection frameworks, that are routinely used with OOP languages go a step
further by decoupling resource usage and creation and allowing live hotswapping.
π The system under test
Brand new micro-service, built with
β Java OOP all the things!
π Spring DI all the things!
External dependencies:
π¬ Database (MySQL)
π Persistent cache (Redis)
π Logging
etc.
In our case, we were building a brand new micro service; built with Java & Spring
As any micro service it also had dependencies on MySQL, Redis, logging, etc.
π Integration testing in the small
Tests multiple components in concert
Lighter-weight than full-blown integration tests
No network involved
No need to boot-up the app server
Spring-supported, developers-approved π€
Since it was rather self-contained, we wanted to try something new, namely Integration in the small.
Integration in the small (some might know it as functional testing) tests multiple components in concert.
It's somewhere in between unit-tests and full-blown integration testing. In our case, that meant that the
network was not involved, and we didn't boot up the app server.
Also, Spring allows for the kind of partial bootstrapping used in these tests, so it was nice that
we had some support out of the box.
π Integration testing in the small – in practice
Mock what you don’t care about with Mockito
Mock shallowly what you do care about
Use MockMVC
to simulate requests to your endpoints and get the responses
Enjoy TestNG π₯
How we went about implementing this was that we mocked the things that weren't the object of the test
by using Mockito. This is a mocking library that basically allows to create a mock out of any object;
by default the mocks have the same interface as the mocked object but all methods are NOPs.
For the things that we actually wanted to test, we implemented shallow mocks ourselves, and let Spring
inject them in all the proper places.
Further down the line, MockMVC (which is part of Spring) allows you to call methods in your service
as if they came over the wire, only that you're actually calling Java methods. It also allows you to
inspect the response, all neatly packed as Java objects.
And in the end, it's business as usual, with TestNG.
π Integration testing in the small – in practice
//The declaration in the micro service
@Autowired
private CacheService redisCache;
…
// Somewhere else, in a file far away – our mock "Redis"
@Service
public class ExpiringMapCacheService implements CacheService {
private final Map<CacheKey, ExpirableValue> map =
Collections.synchronizedMap(
new PassiveExpiringMap<>((key, value) -> value.expire)
);
@Override
public <T> T get(CacheKey key) { … }
// and all the other methods a respectable CacheService has
}
In our case we have the declaration of our Redis client in the service under test, but somewhere down
the line we implement our shallow mock that is basically just a map. Spring takes care of injecting the
mocked cache where it needs to
π Integration testing in the small – in practice
@Test
public void myTest() throws Exception {
MvcResult mvcResult = createRequest.prepare()
.withAuthor(AUTHOR_ID).withBody(BODY_PAYLOAD)
.create()
.andExpect(status().isCreated()).andReturn();
ResourceResponse createResponse =
ResourceResponse.fromMvcResult(mvcResult, mapper);
assertEquals(createResponse.getId(), cacheService.get(KEY).getId());
}
So you could create a request, execute it, make assertions at HTTP level and then check in the
cache for consistency, all within a coupld of lines of code.
π Was it worth it?
We caught some timing-sensitive bugs
We expanded the build-time test suite
Easier debugging for failing tests
β¨ happy developers β¨
The extra effort was definitely worth it:
* we caught some timing sensitive-bugs
* we are running those tests at build time, and they have greater coverage than unit tests
* test code and server code are in the same process, you can debug from one to the other -> happy devs
π€ͺ When I’m alone at night, I Google myself
Support login with social accounts
First implemented Facebook support
π MUCH TESTING FRIENDLY. SUCH WOW
πΏ SUCH SLOW. MUCH FLAKY
Now add Google too π±
One of the features we just released is the ability to use your facebook / Google account to log in
to our service. In terms of how we implemented this, we first added support for facebook, which was
awesome, as they have a lot of support (APIs) for testing.
OTOH their APIs are very slow, flaky and come with some rather draconian rate limits (i.e. no parallel
calls, no more than 2000 users). So we had to have a lot of double checks and retries in place to make
sure we actually get what we asked.
And then we were told we had to add Google in the mix as well. And fast.
π€ͺ Can we test Google?
No way to create test accounts
– without a real phone number behind them
Difficult to automate login from AWS
– unless you still have that phone number
Some tests need fresh, never-before-seen users
– CI, baby! π΄π΅
So we naturally asked ourselves, how do we test for Google?
Because you can't create test accounts - definitely not programatically, and even manually you had to
have a phone number for each account. Automating the log in steps, even when you had the accounts wasn't
easy either, especially when done from within AWS' datacenters, since Google would require phone verification.
And because we run the tests on CI, we needed fresh new accounts for each test run. So yeah, not a pretty picture.
Just to make yourself an idea, that's what our page looked like.
And that's how Google looked like - in our mock.
π€ͺ How did we do it?
π + AWS = π
Reverse engineer the APIs
Reverse engineer the UI π€
Configurable Google endpoints
This was basically a rather simple and plain web service built with python and deployed in the cloud
using Elastic BeanStalk.
Our service was using Google's OAuth APIs, so based on the standard and the public documentation we
reverse engineered the subset of APIs we were using.
Not yet losing hope, we decided it would be nice to have the UI behave the same way as Google, just in
case we find a way to automate at least some flows with real Google accounts.
In the end, we had to do some tweaking to the actual product code to allow using both Google and our
mock side by side, but it was basically just wiring property files and Spring properties
π€ͺ Was it worth it?
We were able to test!
Easier debugging while developing the feature
Decreased test run times
Fun! π
First and foremost this was important because it allowed us to test our Google integration.
On top of that, having a mock that we controlled allowed us to easily reproduce edge cases and it helped
developers as well.
Also, since the mock was really dirt simple, it was also very fast, so the same test suite, when run with
the Google mock, would be 40-50% faster than the suite run with Facebook.
Not last, for me it was fun and very instructive, as I learned a lot about deploying a service on the Internet.
π΅ Nu rΔspunzi la SMS
Add option to reset password via text message
Provider had a staging environment for feature development
Need to do performance testing on our side of the service πΈπΈπΈ
Our third story happened a couple of years ago, when we added the ability to reset your password by
typing in a code received over text vs. a link in your inbox.
We were using an external service provider, that had a staging environment as well - which we used during
feature development.
However, we knew that at one point we had to do some performance testing of the system as a whole and
we were thinking about how to keep the costs down.
Basically that was the UI
π΅ Performance testing gotchas
Provider’s staging environment
– not fast enough
NOP
-ing the calls to the external services
– not an option
Don’t break the bank
– if at all possible
From past as well as first-hand experiences, we knew that there were some gotchas about this kind of load
tests.
First of all, the staging environment we were using was not fast and stable enough to handle the kind
of load we wanted to simulate.
NOP-ing the calls in the server would produce very skewed data, and we wanted to see exactly how all
the network nitty-gritty influenced our performance when calling a third-party service.
Last but not least, we wanted to keep costs low. In another occasion, during a load test for a system
that incidentally was also sending email, we ended up with a bill that could purchase a small family
car - and we wanted to avoid that.
π΅ A mock was born
β Java, π Spring, and not much else
Deployed on AWS, accessible over the Internet
Exposing same API, but always returning success
Product code was already instrumented
π€ + π€ = πͺ
As such, a mock was born.
It was written in Java, Spring and not much else.
We needed it to be available over the internet, to match as closely as possible the final configuration,
so we deployed it in AWS. It was exposing the same APIs as the SMS provider, but they just NOP-ed and
returned success.
On the product side there was no extra effort needed, since we already had this configurable, since we
were using the staging environment for development.
So we started the load test, the throughput was ramping up nicely, but then it stopped well before we
reached the desired load. People scrambled trying to understand where the bottle neck was; it turns out
that the mock had reached its capacity. We raised more machines, put them behind a load balancer and then
the test continued smoothly.
π΅ Was it worth it?
Very π°π°π° much
+100 XP
for our DevOps skills
Paved the way for other creative mocks
First of all, this saved us a lot of money we didn't give to the SMS provider for sending texts out in the void.
The whole ordeal also taught us a thing or two about DevOps and networking and scaling.
Most importantly however, it sparked a Let's do it attitude and paved the way for other mocks, including
the Google mock I talked about earlier